|
@@ -34,9 +34,12 @@
|
|
|
#include "sysemu/balloon.h"
|
|
|
#include "sysemu/hostmem.h"
|
|
|
#include "sysemu/reset.h"
|
|
|
+#include "hv-balloon-our_range_memslots.h"
|
|
|
#include "hv-balloon-page_range_tree.h"
|
|
|
#include "trace.h"
|
|
|
|
|
|
+#define HV_BALLOON_ADDR_PROP "addr"
|
|
|
+#define HV_BALLOON_MEMDEV_PROP "memdev"
|
|
|
#define HV_BALLOON_GUID "525074DC-8985-46e2-8057-A307DC18A502"
|
|
|
|
|
|
/*
|
|
@@ -52,6 +55,8 @@
|
|
|
#define HV_BALLOON_HA_CHUNK_SIZE (2 * GiB)
|
|
|
#define HV_BALLOON_HA_CHUNK_PAGES (HV_BALLOON_HA_CHUNK_SIZE / HV_BALLOON_PAGE_SIZE)
|
|
|
|
|
|
+#define HV_BALLOON_HA_MEMSLOT_SIZE_ALIGN (128 * MiB)
|
|
|
+
|
|
|
#define HV_BALLOON_HR_CHUNK_PAGES 585728
|
|
|
/*
|
|
|
* ^ that's the maximum number of pages
|
|
@@ -88,6 +93,10 @@ typedef enum State {
|
|
|
S_UNBALLOON_POSTING,
|
|
|
S_UNBALLOON_RB_WAIT,
|
|
|
S_UNBALLOON_REPLY_WAIT,
|
|
|
+ S_HOT_ADD_SETUP,
|
|
|
+ S_HOT_ADD_RB_WAIT,
|
|
|
+ S_HOT_ADD_POSTING,
|
|
|
+ S_HOT_ADD_REPLY_WAIT,
|
|
|
} State;
|
|
|
|
|
|
typedef struct StateDesc {
|
|
@@ -117,25 +126,43 @@ typedef struct HvBalloon {
|
|
|
uint64_t target;
|
|
|
bool target_changed;
|
|
|
|
|
|
- /* Current (un)balloon */
|
|
|
+ /* Current (un)balloon / hot-add operation parameters */
|
|
|
union {
|
|
|
uint64_t balloon_diff;
|
|
|
|
|
|
struct {
|
|
|
uint64_t unballoon_diff;
|
|
|
+ uint64_t hot_add_diff;
|
|
|
+ };
|
|
|
+
|
|
|
+ struct {
|
|
|
+ PageRange hot_add_range;
|
|
|
+ uint64_t ha_current_count;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
+ OurRangeMemslots *our_range;
|
|
|
+
|
|
|
+ /* Count of memslots covering our memory */
|
|
|
+ unsigned int memslot_count;
|
|
|
+
|
|
|
/* Nominal size of each memslot (the last one might be smaller) */
|
|
|
uint64_t memslot_size;
|
|
|
|
|
|
+ /* Non-ours removed memory */
|
|
|
PageRangeTree removed_guest, removed_both;
|
|
|
|
|
|
+ /* Grand totals of removed memory (both ours and non-ours) */
|
|
|
uint64_t removed_guest_ctr, removed_both_ctr;
|
|
|
+
|
|
|
+ /* MEMORY_DEVICE props */
|
|
|
+ uint64_t addr;
|
|
|
+ HostMemoryBackend *hostmem;
|
|
|
+ MemoryRegion *mr;
|
|
|
} HvBalloon;
|
|
|
|
|
|
OBJECT_DEFINE_TYPE_WITH_INTERFACES(HvBalloon, hv_balloon, HV_BALLOON, VMBUS_DEVICE, \
|
|
|
- { })
|
|
|
+ { TYPE_MEMORY_DEVICE }, { })
|
|
|
|
|
|
#define HV_BALLOON_SET_STATE(hvb, news) \
|
|
|
do { \
|
|
@@ -155,6 +182,16 @@ typedef struct HvBalloonReq {
|
|
|
VMBusChanReq vmreq;
|
|
|
} HvBalloonReq;
|
|
|
|
|
|
+/* total our memory includes parts currently removed from the guest */
|
|
|
+static uint64_t hv_balloon_total_our_ram(HvBalloon *balloon)
|
|
|
+{
|
|
|
+ if (!balloon->our_range) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return balloon->our_range->range.added;
|
|
|
+}
|
|
|
+
|
|
|
/* TODO: unify the code below with virtio-balloon and cache the value */
|
|
|
static int build_dimm_list(Object *obj, void *opaque)
|
|
|
{
|
|
@@ -193,10 +230,11 @@ static uint64_t hv_balloon_total_ram(HvBalloon *balloon)
|
|
|
{
|
|
|
ram_addr_t ram_size = get_current_ram_size();
|
|
|
uint64_t ram_size_pages = ram_size >> HV_BALLOON_PFN_SHIFT;
|
|
|
+ uint64_t our_ram_size_pages = hv_balloon_total_our_ram(balloon);
|
|
|
|
|
|
assert(ram_size_pages > 0);
|
|
|
|
|
|
- return ram_size_pages;
|
|
|
+ return SUM_SATURATE_U64(ram_size_pages, our_ram_size_pages);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -275,14 +313,30 @@ static ssize_t hv_balloon_send_packet(VMBusChannel *chan,
|
|
|
|
|
|
static bool hv_balloon_unballoon_get_source(HvBalloon *balloon,
|
|
|
PageRangeTree *dtree,
|
|
|
- uint64_t **dctr)
|
|
|
+ uint64_t **dctr,
|
|
|
+ bool *is_our_range)
|
|
|
{
|
|
|
+ OurRange *our_range = OUR_RANGE(balloon->our_range);
|
|
|
+
|
|
|
+ /* Try the boot memory first */
|
|
|
if (g_tree_nnodes(balloon->removed_guest.t) > 0) {
|
|
|
*dtree = balloon->removed_guest;
|
|
|
*dctr = &balloon->removed_guest_ctr;
|
|
|
+ *is_our_range = false;
|
|
|
} else if (g_tree_nnodes(balloon->removed_both.t) > 0) {
|
|
|
*dtree = balloon->removed_both;
|
|
|
*dctr = &balloon->removed_both_ctr;
|
|
|
+ *is_our_range = false;
|
|
|
+ } else if (!our_range) {
|
|
|
+ return false;
|
|
|
+ } else if (!our_range_is_removed_tree_empty(our_range, false)) {
|
|
|
+ *dtree = our_range_get_removed_tree(our_range, false);
|
|
|
+ *dctr = &balloon->removed_guest_ctr;
|
|
|
+ *is_our_range = true;
|
|
|
+ } else if (!our_range_is_removed_tree_empty(our_range, true)) {
|
|
|
+ *dtree = our_range_get_removed_tree(our_range, true);
|
|
|
+ *dctr = &balloon->removed_both_ctr;
|
|
|
+ *is_our_range = true;
|
|
|
} else {
|
|
|
return false;
|
|
|
}
|
|
@@ -310,6 +364,7 @@ static void hv_balloon_unballoon_posting(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
VMBusChannel *chan = hv_balloon_get_channel(balloon);
|
|
|
PageRangeTree dtree;
|
|
|
uint64_t *dctr;
|
|
|
+ bool our_range;
|
|
|
struct dm_unballoon_request *ur;
|
|
|
size_t ur_size = sizeof(*ur) + sizeof(ur->range_array[0]);
|
|
|
PageRange range;
|
|
@@ -319,7 +374,7 @@ static void hv_balloon_unballoon_posting(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
assert(balloon->state == S_UNBALLOON_POSTING);
|
|
|
assert(balloon->unballoon_diff > 0);
|
|
|
|
|
|
- if (!hv_balloon_unballoon_get_source(balloon, &dtree, &dctr)) {
|
|
|
+ if (!hv_balloon_unballoon_get_source(balloon, &dtree, &dctr, &our_range)) {
|
|
|
error_report("trying to unballoon but nothing seems to be ballooned");
|
|
|
/*
|
|
|
* there is little we can do as we might have already
|
|
@@ -328,6 +383,7 @@ static void hv_balloon_unballoon_posting(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ assert(balloon->our_range || !our_range);
|
|
|
assert(dtree.t);
|
|
|
assert(dctr);
|
|
|
|
|
@@ -369,6 +425,166 @@ static void hv_balloon_unballoon_posting(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static bool hv_balloon_our_range_ensure(HvBalloon *balloon)
|
|
|
+{
|
|
|
+ uint64_t align;
|
|
|
+ MemoryRegion *hostmem_mr;
|
|
|
+ g_autoptr(OurRangeMemslots) our_range_memslots = NULL;
|
|
|
+ OurRange *our_range;
|
|
|
+
|
|
|
+ if (balloon->our_range) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!balloon->hostmem) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ align = (1 << balloon->caps.cap_bits.hot_add_alignment) * MiB;
|
|
|
+ assert(QEMU_IS_ALIGNED(balloon->addr, align));
|
|
|
+
|
|
|
+ hostmem_mr = host_memory_backend_get_memory(balloon->hostmem);
|
|
|
+
|
|
|
+ our_range_memslots = hvb_our_range_memslots_new(balloon->addr,
|
|
|
+ balloon->mr, hostmem_mr,
|
|
|
+ OBJECT(balloon),
|
|
|
+ balloon->memslot_count,
|
|
|
+ balloon->memslot_size);
|
|
|
+ our_range = OUR_RANGE(our_range_memslots);
|
|
|
+
|
|
|
+ if (hvb_page_range_tree_intree_any(balloon->removed_guest,
|
|
|
+ our_range->range.start,
|
|
|
+ our_range->range.count) ||
|
|
|
+ hvb_page_range_tree_intree_any(balloon->removed_both,
|
|
|
+ our_range->range.start,
|
|
|
+ our_range->range.count)) {
|
|
|
+ error_report("some parts of the memory backend were already returned by the guest. this should not happen, please reboot the guest and try again");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ trace_hv_balloon_our_range_add(our_range->range.count,
|
|
|
+ our_range->range.start);
|
|
|
+
|
|
|
+ balloon->our_range = g_steal_pointer(&our_range_memslots);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_hot_add_setup(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
+{
|
|
|
+ /* need to make copy since it is in union with hot_add_range */
|
|
|
+ uint64_t hot_add_diff = balloon->hot_add_diff;
|
|
|
+ PageRange *hot_add_range = &balloon->hot_add_range;
|
|
|
+ uint64_t align, our_range_remaining;
|
|
|
+ OurRange *our_range;
|
|
|
+
|
|
|
+ assert(balloon->state == S_HOT_ADD_SETUP);
|
|
|
+ assert(hot_add_diff > 0);
|
|
|
+
|
|
|
+ if (!hv_balloon_our_range_ensure(balloon)) {
|
|
|
+ goto ret_idle;
|
|
|
+ }
|
|
|
+
|
|
|
+ our_range = OUR_RANGE(balloon->our_range);
|
|
|
+
|
|
|
+ align = (1 << balloon->caps.cap_bits.hot_add_alignment) *
|
|
|
+ (MiB / HV_BALLOON_PAGE_SIZE);
|
|
|
+
|
|
|
+ /* Absolute GPA in pages */
|
|
|
+ hot_add_range->start = our_range_get_remaining_start(our_range);
|
|
|
+ assert(QEMU_IS_ALIGNED(hot_add_range->start, align));
|
|
|
+
|
|
|
+ our_range_remaining = our_range_get_remaining_size(our_range);
|
|
|
+ hot_add_range->count = MIN(our_range_remaining, hot_add_diff);
|
|
|
+ hot_add_range->count = QEMU_ALIGN_DOWN(hot_add_range->count, align);
|
|
|
+ if (hot_add_range->count == 0) {
|
|
|
+ goto ret_idle;
|
|
|
+ }
|
|
|
+
|
|
|
+ hvb_our_range_memslots_ensure_mapped_additional(balloon->our_range,
|
|
|
+ hot_add_range->count);
|
|
|
+
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_HOT_ADD_RB_WAIT);
|
|
|
+ return;
|
|
|
+
|
|
|
+ret_idle:
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_IDLE);
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_hot_add_rb_wait(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
+{
|
|
|
+ VMBusChannel *chan = hv_balloon_get_channel(balloon);
|
|
|
+ struct dm_hot_add *ha;
|
|
|
+ size_t ha_size = sizeof(*ha) + sizeof(ha->range);
|
|
|
+
|
|
|
+ assert(balloon->state == S_HOT_ADD_RB_WAIT);
|
|
|
+
|
|
|
+ if (vmbus_channel_reserve(chan, 0, ha_size) < 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_HOT_ADD_POSTING);
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_hot_add_posting(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
+{
|
|
|
+ PageRange *hot_add_range = &balloon->hot_add_range;
|
|
|
+ uint64_t *current_count = &balloon->ha_current_count;
|
|
|
+ VMBusChannel *chan = hv_balloon_get_channel(balloon);
|
|
|
+ struct dm_hot_add *ha;
|
|
|
+ size_t ha_size = sizeof(*ha) + sizeof(ha->range);
|
|
|
+ union dm_mem_page_range *ha_region;
|
|
|
+ uint64_t align, chunk_max_size;
|
|
|
+ ssize_t ret;
|
|
|
+
|
|
|
+ assert(balloon->state == S_HOT_ADD_POSTING);
|
|
|
+ assert(hot_add_range->count > 0);
|
|
|
+
|
|
|
+ align = (1 << balloon->caps.cap_bits.hot_add_alignment) *
|
|
|
+ (MiB / HV_BALLOON_PAGE_SIZE);
|
|
|
+ if (align >= HV_BALLOON_HA_CHUNK_PAGES) {
|
|
|
+ /*
|
|
|
+ * If the required alignment is higher than the chunk size we let it
|
|
|
+ * override that size.
|
|
|
+ */
|
|
|
+ chunk_max_size = align;
|
|
|
+ } else {
|
|
|
+ chunk_max_size = QEMU_ALIGN_DOWN(HV_BALLOON_HA_CHUNK_PAGES, align);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * hot_add_range->count starts aligned in hv_balloon_hot_add_setup(),
|
|
|
+ * then it is either reduced by subtracting aligned current_count or
|
|
|
+ * further hot-adds are prevented by marking the whole remaining our range
|
|
|
+ * as unusable in hv_balloon_handle_hot_add_response().
|
|
|
+ */
|
|
|
+ *current_count = MIN(hot_add_range->count, chunk_max_size);
|
|
|
+
|
|
|
+ ha = alloca(ha_size);
|
|
|
+ ha_region = &(&ha->range)[1];
|
|
|
+ memset(ha, 0, ha_size);
|
|
|
+ ha->hdr.type = DM_MEM_HOT_ADD_REQUEST;
|
|
|
+ ha->hdr.size = ha_size;
|
|
|
+ ha->hdr.trans_id = balloon->trans_id;
|
|
|
+
|
|
|
+ ha->range.finfo.start_page = hot_add_range->start;
|
|
|
+ ha->range.finfo.page_cnt = *current_count;
|
|
|
+ ha_region->finfo.start_page = hot_add_range->start;
|
|
|
+ ha_region->finfo.page_cnt = ha->range.finfo.page_cnt;
|
|
|
+
|
|
|
+ trace_hv_balloon_outgoing_hot_add(ha->hdr.trans_id,
|
|
|
+ *current_count, hot_add_range->start);
|
|
|
+
|
|
|
+ ret = vmbus_channel_send(chan, VMBUS_PACKET_DATA_INBAND,
|
|
|
+ NULL, 0, ha, ha_size, false,
|
|
|
+ ha->hdr.trans_id);
|
|
|
+ if (ret <= 0) {
|
|
|
+ error_report("error %zd when posting hot add msg, expect problems",
|
|
|
+ ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_HOT_ADD_REPLY_WAIT);
|
|
|
+}
|
|
|
+
|
|
|
static void hv_balloon_balloon_rb_wait(HvBalloon *balloon, StateDesc *stdesc)
|
|
|
{
|
|
|
VMBusChannel *chan = hv_balloon_get_channel(balloon);
|
|
@@ -428,14 +644,23 @@ static void hv_balloon_idle_state_process_target(HvBalloon *balloon,
|
|
|
* the procedure is in progress
|
|
|
*/
|
|
|
if (balloon->target > ram_size_pages - total_removed) {
|
|
|
+ bool can_hot_add = balloon->caps.cap_bits.hot_add;
|
|
|
uint64_t target_diff = balloon->target -
|
|
|
(ram_size_pages - total_removed);
|
|
|
|
|
|
balloon->unballoon_diff = MIN(target_diff, total_removed);
|
|
|
|
|
|
+ if (can_hot_add) {
|
|
|
+ balloon->hot_add_diff = target_diff - balloon->unballoon_diff;
|
|
|
+ } else {
|
|
|
+ balloon->hot_add_diff = 0;
|
|
|
+ }
|
|
|
+
|
|
|
if (balloon->unballoon_diff > 0) {
|
|
|
assert(can_balloon);
|
|
|
HV_BALLOON_STATE_DESC_SET(stdesc, S_UNBALLOON_RB_WAIT);
|
|
|
+ } else if (balloon->hot_add_diff > 0) {
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_HOT_ADD_SETUP);
|
|
|
}
|
|
|
} else if (can_balloon &&
|
|
|
balloon->target < ram_size_pages - total_removed) {
|
|
@@ -465,6 +690,9 @@ static const struct {
|
|
|
[S_BALLOON_RB_WAIT].handler = hv_balloon_balloon_rb_wait,
|
|
|
[S_UNBALLOON_POSTING].handler = hv_balloon_unballoon_posting,
|
|
|
[S_UNBALLOON_RB_WAIT].handler = hv_balloon_unballoon_rb_wait,
|
|
|
+ [S_HOT_ADD_SETUP].handler = hv_balloon_hot_add_setup,
|
|
|
+ [S_HOT_ADD_RB_WAIT].handler = hv_balloon_hot_add_rb_wait,
|
|
|
+ [S_HOT_ADD_POSTING].handler = hv_balloon_hot_add_posting,
|
|
|
};
|
|
|
|
|
|
static void hv_balloon_handle_state(HvBalloon *balloon, StateDesc *stdesc)
|
|
@@ -507,13 +735,64 @@ static void hv_balloon_remove_response_handle_range(HvBalloon *balloon,
|
|
|
bool both,
|
|
|
uint64_t *removedctr)
|
|
|
{
|
|
|
+ OurRange *our_range = OUR_RANGE(balloon->our_range);
|
|
|
PageRangeTree globaltree =
|
|
|
both ? balloon->removed_both : balloon->removed_guest;
|
|
|
uint64_t *globalctr =
|
|
|
both ? &balloon->removed_both_ctr : &balloon->removed_guest_ctr;
|
|
|
+ PageRange rangeeff;
|
|
|
+
|
|
|
+ if (range->count == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
trace_hv_balloon_remove_response(range->count, range->start, both);
|
|
|
|
|
|
+ if (our_range) {
|
|
|
+ /* Includes the not-yet-hot-added and unusable parts. */
|
|
|
+ rangeeff = our_range->range;
|
|
|
+ } else {
|
|
|
+ rangeeff.start = rangeeff.count = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (page_range_intersection_size(range, rangeeff.start, rangeeff.count) > 0) {
|
|
|
+ PageRangeTree ourtree = our_range_get_removed_tree(our_range, both);
|
|
|
+ PageRange rangehole, rangecommon;
|
|
|
+ uint64_t ourremoved = 0;
|
|
|
+
|
|
|
+ /* process the hole before our range, if it exists */
|
|
|
+ page_range_part_before(range, rangeeff.start, &rangehole);
|
|
|
+ hv_balloon_remove_response_insert_range(globaltree, &rangehole,
|
|
|
+ globalctr, removedctr, NULL);
|
|
|
+ if (rangehole.count > 0) {
|
|
|
+ trace_hv_balloon_remove_response_hole(rangehole.count,
|
|
|
+ rangehole.start,
|
|
|
+ range->count, range->start,
|
|
|
+ rangeeff.start, both);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* process our part */
|
|
|
+ page_range_intersect(range, rangeeff.start, rangeeff.count,
|
|
|
+ &rangecommon);
|
|
|
+ hv_balloon_remove_response_insert_range(ourtree, &rangecommon,
|
|
|
+ globalctr, removedctr,
|
|
|
+ &ourremoved);
|
|
|
+ if (rangecommon.count > 0) {
|
|
|
+ trace_hv_balloon_remove_response_common(rangecommon.count,
|
|
|
+ rangecommon.start,
|
|
|
+ range->count, range->start,
|
|
|
+ rangeeff.count,
|
|
|
+ rangeeff.start, ourremoved,
|
|
|
+ both);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* calculate what's left after our range */
|
|
|
+ rangecommon = *range;
|
|
|
+ page_range_part_after(&rangecommon, rangeeff.start, rangeeff.count,
|
|
|
+ range);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* process the remainder of the range that lies after our range */
|
|
|
if (range->count > 0) {
|
|
|
hv_balloon_remove_response_insert_range(globaltree, range,
|
|
|
globalctr, removedctr, NULL);
|
|
@@ -847,6 +1126,72 @@ static void hv_balloon_handle_unballoon_response(HvBalloon *balloon,
|
|
|
|
|
|
balloon->trans_id++;
|
|
|
|
|
|
+ if (balloon->hot_add_diff > 0) {
|
|
|
+ bool can_hot_add = balloon->caps.cap_bits.hot_add;
|
|
|
+
|
|
|
+ assert(can_hot_add);
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_HOT_ADD_SETUP);
|
|
|
+ } else {
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_IDLE);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_handle_hot_add_response(HvBalloon *balloon,
|
|
|
+ HvBalloonReq *req,
|
|
|
+ StateDesc *stdesc)
|
|
|
+{
|
|
|
+ PageRange *hot_add_range = &balloon->hot_add_range;
|
|
|
+ VMBusChanReq *vmreq = &req->vmreq;
|
|
|
+ struct dm_hot_add_response *msgHaR = vmreq->msg;
|
|
|
+ OurRange *our_range;
|
|
|
+
|
|
|
+ if (balloon->state != S_HOT_ADD_REPLY_WAIT) {
|
|
|
+ warn_report("unexpected DM_HOT_ADD_RESPONSE in %d state",
|
|
|
+ balloon->state);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ assert(balloon->our_range);
|
|
|
+ our_range = OUR_RANGE(balloon->our_range);
|
|
|
+
|
|
|
+ if (!hv_balloon_handle_msg_size(req, sizeof(*msgHaR),
|
|
|
+ "DM_HOT_ADD_RESPONSE"))
|
|
|
+ return;
|
|
|
+
|
|
|
+ trace_hv_balloon_incoming_hot_add(msgHaR->hdr.trans_id, msgHaR->result,
|
|
|
+ msgHaR->page_count);
|
|
|
+
|
|
|
+ balloon->trans_id++;
|
|
|
+
|
|
|
+ if (msgHaR->result) {
|
|
|
+ if (msgHaR->page_count > balloon->ha_current_count) {
|
|
|
+ warn_report("DM_HOT_ADD_RESPONSE page count higher than requested (%"PRIu32" vs %"PRIu64")",
|
|
|
+ msgHaR->page_count, balloon->ha_current_count);
|
|
|
+ msgHaR->page_count = balloon->ha_current_count;
|
|
|
+ }
|
|
|
+
|
|
|
+ hvb_our_range_mark_added(our_range, msgHaR->page_count);
|
|
|
+ hot_add_range->start += msgHaR->page_count;
|
|
|
+ hot_add_range->count -= msgHaR->page_count;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!msgHaR->result || msgHaR->page_count < balloon->ha_current_count) {
|
|
|
+ /*
|
|
|
+ * the current planned range was only partially hot-added, take note
|
|
|
+ * how much of it remains and don't attempt any further hot adds
|
|
|
+ */
|
|
|
+ our_range_mark_remaining_unusable(our_range);
|
|
|
+
|
|
|
+ goto ret_idle;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* any pages remaining to hot-add in our range? */
|
|
|
+ if (hot_add_range->count > 0) {
|
|
|
+ HV_BALLOON_STATE_DESC_SET(stdesc, S_HOT_ADD_RB_WAIT);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ret_idle:
|
|
|
HV_BALLOON_STATE_DESC_SET(stdesc, S_IDLE);
|
|
|
}
|
|
|
|
|
@@ -924,6 +1269,10 @@ static void hv_balloon_handle_packet(HvBalloon *balloon, HvBalloonReq *req,
|
|
|
hv_balloon_handle_status_report(balloon, req);
|
|
|
break;
|
|
|
|
|
|
+ case DM_MEM_HOT_ADD_RESPONSE:
|
|
|
+ hv_balloon_handle_hot_add_response(balloon, req, stdesc);
|
|
|
+ break;
|
|
|
+
|
|
|
case DM_UNBALLOON_RESPONSE:
|
|
|
hv_balloon_handle_unballoon_response(balloon, req, stdesc);
|
|
|
break;
|
|
@@ -1072,6 +1421,61 @@ static void hv_balloon_post_init_timer(void *opaque)
|
|
|
hv_balloon_event_loop(balloon);
|
|
|
}
|
|
|
|
|
|
+static void hv_balloon_system_reset_unrealize_common(HvBalloon *balloon)
|
|
|
+{
|
|
|
+ g_clear_pointer(&balloon->our_range, hvb_our_range_memslots_free);
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_system_reset(void *opaque)
|
|
|
+{
|
|
|
+ HvBalloon *balloon = HV_BALLOON(opaque);
|
|
|
+
|
|
|
+ hv_balloon_system_reset_unrealize_common(balloon);
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_ensure_mr(HvBalloon *balloon)
|
|
|
+{
|
|
|
+ MemoryRegion *hostmem_mr;
|
|
|
+
|
|
|
+ assert(balloon->hostmem);
|
|
|
+
|
|
|
+ if (balloon->mr) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ hostmem_mr = host_memory_backend_get_memory(balloon->hostmem);
|
|
|
+
|
|
|
+ balloon->mr = g_new0(MemoryRegion, 1);
|
|
|
+ memory_region_init(balloon->mr, OBJECT(balloon), TYPE_HV_BALLOON,
|
|
|
+ memory_region_size(hostmem_mr));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The VM can indicate an alignment up to 32 GiB. Memory device core can
|
|
|
+ * usually only handle/guarantee 1 GiB alignment. The user will have to
|
|
|
+ * specify a larger maxmem eventually.
|
|
|
+ *
|
|
|
+ * The memory device core will warn the user in case maxmem might have to be
|
|
|
+ * increased and will fail plugging the device if there is not sufficient
|
|
|
+ * space after alignment.
|
|
|
+ *
|
|
|
+ * TODO: we could do the alignment ourselves in a slightly bigger region.
|
|
|
+ * But this feels better, although the warning might be annoying. Maybe
|
|
|
+ * we can optimize that in the future (e.g., with such a device on the
|
|
|
+ * cmdline place/size the device memory region differently.
|
|
|
+ */
|
|
|
+ balloon->mr->align = MAX(32 * GiB, memory_region_get_alignment(hostmem_mr));
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_free_mr(HvBalloon *balloon)
|
|
|
+{
|
|
|
+ if (!balloon->mr) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ object_unparent(OBJECT(balloon->mr));
|
|
|
+ g_clear_pointer(&balloon->mr, g_free);
|
|
|
+}
|
|
|
+
|
|
|
static void hv_balloon_vmdev_realize(VMBusDevice *vdev, Error **errp)
|
|
|
{
|
|
|
ERRP_GUARD();
|
|
@@ -1088,13 +1492,52 @@ static void hv_balloon_vmdev_realize(VMBusDevice *vdev, Error **errp)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (balloon->hostmem) {
|
|
|
+ if (host_memory_backend_is_mapped(balloon->hostmem)) {
|
|
|
+ Object *obj = OBJECT(balloon->hostmem);
|
|
|
+
|
|
|
+ error_setg(errp, "'%s' property specifies a busy memdev: %s",
|
|
|
+ HV_BALLOON_MEMDEV_PROP,
|
|
|
+ object_get_canonical_path_component(obj));
|
|
|
+ goto out_balloon_handler;
|
|
|
+ }
|
|
|
+
|
|
|
+ hv_balloon_ensure_mr(balloon);
|
|
|
+
|
|
|
+ /* This is rather unlikely to happen, but let's still check for it. */
|
|
|
+ if (!QEMU_IS_ALIGNED(memory_region_size(balloon->mr),
|
|
|
+ HV_BALLOON_PAGE_SIZE)) {
|
|
|
+ error_setg(errp, "'%s' property memdev size has to be a multiple of 0x%" PRIx64,
|
|
|
+ HV_BALLOON_MEMDEV_PROP, (uint64_t)HV_BALLOON_PAGE_SIZE);
|
|
|
+ goto out_balloon_handler;
|
|
|
+ }
|
|
|
+
|
|
|
+ host_memory_backend_set_mapped(balloon->hostmem, true);
|
|
|
+ vmstate_register_ram(host_memory_backend_get_memory(balloon->hostmem),
|
|
|
+ DEVICE(balloon));
|
|
|
+ } else if (balloon->addr) {
|
|
|
+ error_setg(errp, "'%s' property must not be set without a memdev",
|
|
|
+ HV_BALLOON_MEMDEV_PROP);
|
|
|
+ goto out_balloon_handler;
|
|
|
+ }
|
|
|
+
|
|
|
timer_init_ms(&balloon->post_init_timer, QEMU_CLOCK_VIRTUAL,
|
|
|
hv_balloon_post_init_timer, balloon);
|
|
|
+
|
|
|
+ qemu_register_reset(hv_balloon_system_reset, balloon);
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+out_balloon_handler:
|
|
|
+ qemu_remove_balloon_handler(balloon);
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* VMBus device reset has to be implemented in case the guest decides to
|
|
|
* disconnect and reconnect to the VMBus without rebooting the whole system.
|
|
|
+ *
|
|
|
+ * However, the hot-added memory can't be removed here as Windows keeps on using
|
|
|
+ * it until the system is restarted, even after disconnecting from the VMBus.
|
|
|
*/
|
|
|
static void hv_balloon_vmdev_reset(VMBusDevice *vdev)
|
|
|
{
|
|
@@ -1104,6 +1547,10 @@ static void hv_balloon_vmdev_reset(VMBusDevice *vdev)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (balloon->our_range) {
|
|
|
+ hvb_our_range_clear_removed_trees(OUR_RANGE(balloon->our_range));
|
|
|
+ }
|
|
|
+
|
|
|
hvb_page_range_tree_destroy(&balloon->removed_guest);
|
|
|
hvb_page_range_tree_destroy(&balloon->removed_both);
|
|
|
hvb_page_range_tree_init(&balloon->removed_guest);
|
|
@@ -1117,14 +1564,106 @@ static void hv_balloon_vmdev_reset(VMBusDevice *vdev)
|
|
|
hv_balloon_event_loop(balloon);
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Clean up things that were (possibly) allocated pre-realization, for example
|
|
|
+ * from memory_device_pre_plug(), so we don't leak them if the device don't
|
|
|
+ * actually get realized in the end.
|
|
|
+ */
|
|
|
+static void hv_balloon_unrealize_finalize_common(HvBalloon *balloon)
|
|
|
+{
|
|
|
+ hv_balloon_free_mr(balloon);
|
|
|
+ balloon->addr = 0;
|
|
|
+
|
|
|
+ balloon->memslot_count = 0;
|
|
|
+}
|
|
|
+
|
|
|
static void hv_balloon_vmdev_unrealize(VMBusDevice *vdev)
|
|
|
{
|
|
|
HvBalloon *balloon = HV_BALLOON(vdev);
|
|
|
|
|
|
+ qemu_unregister_reset(hv_balloon_system_reset, balloon);
|
|
|
+
|
|
|
+ hv_balloon_system_reset_unrealize_common(balloon);
|
|
|
+
|
|
|
qemu_remove_balloon_handler(balloon);
|
|
|
|
|
|
+ if (balloon->hostmem) {
|
|
|
+ vmstate_unregister_ram(host_memory_backend_get_memory(balloon->hostmem),
|
|
|
+ DEVICE(balloon));
|
|
|
+ host_memory_backend_set_mapped(balloon->hostmem, false);
|
|
|
+ }
|
|
|
+
|
|
|
hvb_page_range_tree_destroy(&balloon->removed_guest);
|
|
|
hvb_page_range_tree_destroy(&balloon->removed_both);
|
|
|
+
|
|
|
+ hv_balloon_unrealize_finalize_common(balloon);
|
|
|
+}
|
|
|
+
|
|
|
+static uint64_t hv_balloon_md_get_addr(const MemoryDeviceState *md)
|
|
|
+{
|
|
|
+ return object_property_get_uint(OBJECT(md), HV_BALLOON_ADDR_PROP,
|
|
|
+ &error_abort);
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_md_set_addr(MemoryDeviceState *md, uint64_t addr,
|
|
|
+ Error **errp)
|
|
|
+{
|
|
|
+ object_property_set_uint(OBJECT(md), HV_BALLOON_ADDR_PROP, addr, errp);
|
|
|
+}
|
|
|
+
|
|
|
+static MemoryRegion *hv_balloon_md_get_memory_region(MemoryDeviceState *md,
|
|
|
+ Error **errp)
|
|
|
+{
|
|
|
+ HvBalloon *balloon = HV_BALLOON(md);
|
|
|
+
|
|
|
+ if (!balloon->hostmem) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ hv_balloon_ensure_mr(balloon);
|
|
|
+
|
|
|
+ return balloon->mr;
|
|
|
+}
|
|
|
+
|
|
|
+static void hv_balloon_decide_memslots(MemoryDeviceState *md,
|
|
|
+ unsigned int limit)
|
|
|
+{
|
|
|
+ HvBalloon *balloon = HV_BALLOON(md);
|
|
|
+ MemoryRegion *hostmem_mr;
|
|
|
+ uint64_t region_size, memslot_size, memslots;
|
|
|
+
|
|
|
+ /* We're called exactly once, before realizing the device. */
|
|
|
+ assert(!balloon->memslot_count);
|
|
|
+
|
|
|
+ /* We should not be called if we don't have a memory backend */
|
|
|
+ assert(balloon->hostmem);
|
|
|
+
|
|
|
+ hostmem_mr = host_memory_backend_get_memory(balloon->hostmem);
|
|
|
+ region_size = memory_region_size(hostmem_mr);
|
|
|
+
|
|
|
+ assert(region_size > 0);
|
|
|
+ memslot_size = QEMU_ALIGN_UP(region_size / limit,
|
|
|
+ HV_BALLOON_HA_MEMSLOT_SIZE_ALIGN);
|
|
|
+ memslots = QEMU_ALIGN_UP(region_size, memslot_size) / memslot_size;
|
|
|
+
|
|
|
+ if (memslots > 1) {
|
|
|
+ balloon->memslot_size = memslot_size;
|
|
|
+ } else {
|
|
|
+ balloon->memslot_size = region_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ assert(memslots <= UINT_MAX);
|
|
|
+ balloon->memslot_count = memslots;
|
|
|
+}
|
|
|
+
|
|
|
+static unsigned int hv_balloon_get_memslots(MemoryDeviceState *md)
|
|
|
+{
|
|
|
+ const HvBalloon *balloon = HV_BALLOON(md);
|
|
|
+
|
|
|
+ /* We're called after setting the suggested limit. */
|
|
|
+ assert(balloon->memslot_count > 0);
|
|
|
+
|
|
|
+ return balloon->memslot_count;
|
|
|
}
|
|
|
|
|
|
static void hv_balloon_init(Object *obj)
|
|
@@ -1133,12 +1672,20 @@ static void hv_balloon_init(Object *obj)
|
|
|
|
|
|
static void hv_balloon_finalize(Object *obj)
|
|
|
{
|
|
|
+ HvBalloon *balloon = HV_BALLOON(obj);
|
|
|
+
|
|
|
+ hv_balloon_unrealize_finalize_common(balloon);
|
|
|
}
|
|
|
|
|
|
static Property hv_balloon_properties[] = {
|
|
|
DEFINE_PROP_BOOL("status-report", HvBalloon,
|
|
|
status_report.enabled, false),
|
|
|
|
|
|
+ /* MEMORY_DEVICE props */
|
|
|
+ DEFINE_PROP_LINK(HV_BALLOON_MEMDEV_PROP, HvBalloon, hostmem,
|
|
|
+ TYPE_MEMORY_BACKEND, HostMemoryBackend *),
|
|
|
+ DEFINE_PROP_UINT64(HV_BALLOON_ADDR_PROP, HvBalloon, addr, 0),
|
|
|
+
|
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
|
};
|
|
|
|
|
@@ -1146,6 +1693,7 @@ static void hv_balloon_class_init(ObjectClass *klass, void *data)
|
|
|
{
|
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
VMBusDeviceClass *vdc = VMBUS_DEVICE_CLASS(klass);
|
|
|
+ MemoryDeviceClass *mdc = MEMORY_DEVICE_CLASS(klass);
|
|
|
|
|
|
device_class_set_props(dc, hv_balloon_properties);
|
|
|
qemu_uuid_parse(HV_BALLOON_GUID, &vdc->classid);
|
|
@@ -1157,4 +1705,12 @@ static void hv_balloon_class_init(ObjectClass *klass, void *data)
|
|
|
vdc->open_channel = hv_balloon_vmdev_open_channel;
|
|
|
vdc->close_channel = hv_balloon_vmdev_close_channel;
|
|
|
vdc->chan_notify_cb = hv_balloon_vmdev_chan_notify;
|
|
|
+
|
|
|
+ mdc->get_addr = hv_balloon_md_get_addr;
|
|
|
+ mdc->set_addr = hv_balloon_md_set_addr;
|
|
|
+ mdc->get_plugged_size = memory_device_get_region_size;
|
|
|
+ mdc->get_memory_region = hv_balloon_md_get_memory_region;
|
|
|
+ mdc->decide_memslots = hv_balloon_decide_memslots;
|
|
|
+ mdc->get_memslots = hv_balloon_get_memslots;
|
|
|
+ /* implement fill_device_info */
|
|
|
}
|