diff --git a/qubes/qmemman/__init__.py b/qubes/qmemman/__init__.py
index 9876db4c4..7dcdf68af 100644
--- a/qubes/qmemman/__init__.py
+++ b/qubes/qmemman/__init__.py
@@ -44,6 +44,7 @@ def __init__(self, id):
self.mem_used = None # used memory, computed based on meminfo
self.id = id # domain id
self.last_target = 0 # the last memset target
+ self.use_hoplug = False # use memory hotplug for mem-set
self.no_progress = False # no react to memset
self.slow_memset_react = False # slow react to memset (after few
# tries still above target)
@@ -96,7 +97,7 @@ def get_free_xen_memory(self):
# used - do not count it as "free", because domain is free to use it
# at any time
# assumption: self.refresh_memactual was called before
- # (so domdict[id].memory_actual is up to date)
+ # (so domdict[id].memory_actual is up-to-date)
assigned_but_unused = functools.reduce(
lambda acc, dom: acc + max(0, dom.last_target-dom.memory_current),
self.domdict.values(),
@@ -127,9 +128,16 @@ def refresh_memactual(self):
self.domdict[id].memory_current,
self.domdict[id].last_target
)
- self.domdict[id].memory_maximum = self.xs.read('', '/local/domain/%s/memory/static-max' % str(id))
- if self.domdict[id].memory_maximum:
- self.domdict[id].memory_maximum = int(self.domdict[id].memory_maximum)*1024
+ hotplug_max = self.xs.read(
+ '', '/local/domain/%s/memory/hotplug-max' % str(id))
+ static_max = self.xs.read(
+ '', '/local/domain/%s/memory/static-max' % str(id))
+ if hotplug_max:
+ self.domdict[id].memory_maximum = int(hotplug_max)*1024
+ self.domdict[id].use_hotplug = True
+ elif static_max:
+ self.domdict[id].memory_maximum = int(static_max)*1024
+ self.domdict[id].use_hotplug = False
else:
self.domdict[id].memory_maximum = self.ALL_PHYS_MEM
# the previous line used to be
@@ -174,6 +182,9 @@ def mem_set(self, id, val):
# handle Xen view of memory
self.xs.write('', '/local/domain/' + id + '/memory/target',
str(int(val/1024 - 16 * 1024)))
+ if self.domdict[id].use_hotplug:
+ self.xs.write('', '/local/domain/' + id + '/memory/static-max',
+ str(int(val / 1024)))
# this is called at the end of ballooning, when we have Xen free mem already
# make sure that past mem_set will not decrease Xen free mem
diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py
index 5cc90489a..473705520 100644
--- a/qubes/vm/qubesvm.py
+++ b/qubes/vm/qubesvm.py
@@ -23,6 +23,7 @@
import asyncio
import base64
import grp
+import pathlib
import re
import os
import os.path
@@ -1627,6 +1628,28 @@ def is_memory_balancing_possible(self):
return False
return True
+ @property
+ def use_memory_hotplug(self):
+ """Use memory hotplug for memory balancing.
+ This is preferred if supported, because it has less initial overhead
+ and reduces Xen's attack surface.
+ The support needs to be enabled in the VM's kernel.
+ """
+ feature = self.features.check_with_template('memory-hotplug', None)
+ if feature is not None:
+ return bool(feature)
+ # if not explicitly set, check if support is advertised
+ # for dom0-provided kernel - check there
+ if self.kernel:
+ return (pathlib.Path(self.storage.kernels_dir) /
+ 'memory-hotplug-supported').exists()
+ # otherwise - check advertised VM's features
+ feature = self.features.check_with_template(
+ 'supported-feature.memory-hotplug', None)
+ if feature is not None:
+ return bool(feature)
+ return False
+
def request_memory(self, mem_required=None):
if not qmemman_present:
return None
@@ -2277,6 +2300,10 @@ def create_qdb_entries(self):
self.app.vmm.xs.set_permissions('',
f"{xs_basedir}/memory/meminfo",
[{'dom': self.xid}])
+ if self.use_memory_hotplug:
+ self.app.vmm.xs.write('',
+ '/local/domain/{}/memory/hotplug-max',
+ str(self.maxmem))
self.fire_event('domain-qdb-create')
diff --git a/templates/libvirt/xen.xml b/templates/libvirt/xen.xml
index a818c3482..83cedf3a3 100644
--- a/templates/libvirt/xen.xml
+++ b/templates/libvirt/xen.xml
@@ -3,7 +3,7 @@
{{ vm.name }}
{{ vm.uuid }}
{% if ((vm.virt_mode == 'hvm' and vm.devices['pci'].persistent() | list)
- or vm.maxmem == 0) -%}
+ or vm.maxmem == 0 or vm.use_memory_hotplug) -%}
{{ vm.memory }}
{% else -%}
{{ vm.maxmem }}