Add a map granule abstraction that decouples HVF memory mapping
alignment from the host page size. On ARM64, Apple Silicon supports
stage-2 translation with 4KB page granules regardless of the host OS
page size (16KB on macOS). This allows running guests with 4KB pages
without interior mapping failures.
Introduce hvf_set_map_granule()/hvf_get_map_granule() in hvf-all.c
to replace hard-coded qemu_real_host_page_size() references in
do_hv_vm_protect() and hvf_set_phys_mem(). When the granule is not
explicitly configured, fall back to host page size to preserve the
previous behaviour.
Add an "ipa-granule" accelerator property (auto, 4k, 16k) following
the established kvm_arch_accel_class_init() pattern: ARM registers
the property in hvf_arch_accel_class_init(), x86 provides an empty
stub. This avoids #ifdef __aarch64__ in common code while keeping
a single hvf-all.c shared by both targets.
Fix hvf_set_phys_mem() to return early for non-aligned regions instead
of clearing the add flag, which previously fell through to an
incorrect unmap of a region that was never mapped.
Signed-off-by: Lucas Amaral <lucas@lucasamaral.com>
---
accel/hvf/hvf-all.c | 30 +++++++++++++++++++++++++++---
include/system/hvf.h | 15 +++++++++++++++
include/system/hvf_int.h | 2 ++
target/arm/hvf/hvf.c | 40 ++++++++++++++++++++++++++++++++++++++++
target/i386/hvf/hvf.c | 4 ++++
5 files changed, 88 insertions(+), 3 deletions(-)
diff --git a/accel/hvf/hvf-all.c b/accel/hvf/hvf-all.c
index 5f357c6d..d745fefd 100644
--- a/accel/hvf/hvf-all.c
+++ b/accel/hvf/hvf-all.c
@@ -10,6 +10,8 @@
#include "qemu/osdep.h"
#include "qemu/error-report.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
#include "accel/accel-ops.h"
#include "exec/cpu-common.h"
#include "system/address-spaces.h"
@@ -22,6 +24,21 @@
bool hvf_allowed;
+static uint64_t hvf_map_granule;
+
+void hvf_set_map_granule(uint64_t size)
+{
+ hvf_map_granule = size;
+}
+
+uint64_t hvf_get_map_granule(void)
+{
+ if (!hvf_map_granule) {
+ return qemu_real_host_page_size();
+ }
+ return hvf_map_granule;
+}
+
const char *hvf_return_string(hv_return_t ret)
{
switch (ret) {
@@ -53,7 +70,7 @@ void assert_hvf_ok_impl(hv_return_t ret, const char *file, unsigned int line,
static void do_hv_vm_protect(hwaddr start, size_t size,
hv_memory_flags_t flags)
{
- intptr_t page_mask = qemu_real_host_page_mask();
+ intptr_t page_mask = -(intptr_t)hvf_get_map_granule();
hv_return_t ret;
trace_hvf_vm_protect(start, size, flags,
@@ -83,7 +100,7 @@ static void hvf_set_phys_mem(MemoryRegionSection *section, bool add)
MemoryRegion *area = section->mr;
bool writable = !area->readonly && !area->rom_device;
hv_memory_flags_t flags;
- uint64_t page_size = qemu_real_host_page_size();
+ uint64_t page_size = hvf_get_map_granule();
uint64_t gpa = section->offset_within_address_space;
uint64_t size = int128_get64(section->size);
hv_return_t ret;
@@ -104,7 +121,7 @@ static void hvf_set_phys_mem(MemoryRegionSection *section, bool add)
if (!QEMU_IS_ALIGNED(size, page_size) ||
!QEMU_IS_ALIGNED(gpa, page_size)) {
/* Not page aligned, so we can not map as RAM */
- add = false;
+ return;
}
if (!add) {
@@ -186,6 +203,11 @@ static int hvf_accel_init(AccelState *as, MachineState *ms)
int pa_range = 36;
MachineClass *mc = MACHINE_GET_CLASS(ms);
+ /* Resolve ipa-granule=auto → host page size */
+ if (!s->ipa_granule) {
+ s->ipa_granule = qemu_real_host_page_size();
+ }
+ hvf_set_map_granule(s->ipa_granule);
if (mc->get_physical_address_range) {
pa_range = mc->get_physical_address_range(ms,
@@ -223,6 +245,8 @@ static void hvf_accel_class_init(ObjectClass *oc, const void *data)
ac->init_machine = hvf_accel_init;
ac->allowed = &hvf_allowed;
ac->gdbstub_supported_sstep_flags = hvf_gdbstub_sstep_flags;
+
+ hvf_arch_accel_class_init(oc);
}
static const TypeInfo hvf_accel_type = {
diff --git a/include/system/hvf.h b/include/system/hvf.h
index d3dcf088..3c4c3b89 100644
--- a/include/system/hvf.h
+++ b/include/system/hvf.h
@@ -36,4 +36,19 @@ typedef struct HVFState HVFState;
DECLARE_INSTANCE_CHECKER(HVFState, HVF_STATE,
TYPE_HVF_ACCEL)
+#ifdef CONFIG_HVF_IS_POSSIBLE
+/*
+ * Minimum alignment for hv_vm_map(). Returns the configured IPA granule
+ * or host page size if not set.
+ */
+void hvf_set_map_granule(uint64_t size);
+uint64_t hvf_get_map_granule(void);
+#else
+static inline void hvf_set_map_granule(uint64_t size) {}
+static inline uint64_t hvf_get_map_granule(void)
+{
+ return qemu_real_host_page_size();
+}
+#endif
+
#endif
diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
index 2621164c..9589b022 100644
--- a/include/system/hvf_int.h
+++ b/include/system/hvf_int.h
@@ -38,6 +38,7 @@ struct HVFState {
hvf_vcpu_caps *hvf_caps;
uint64_t vtimer_offset;
+ uint32_t ipa_granule;
QTAILQ_HEAD(, hvf_sw_breakpoint) hvf_sw_breakpoints;
};
extern HVFState *hvf_state;
@@ -57,6 +58,7 @@ void assert_hvf_ok_impl(hv_return_t ret, const char *file, unsigned int line,
const char *hvf_return_string(hv_return_t ret);
int hvf_arch_init(void);
hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range);
+void hvf_arch_accel_class_init(ObjectClass *oc);
uint32_t hvf_arch_get_default_ipa_bit_size(void);
uint32_t hvf_arch_get_max_ipa_bit_size(void);
void hvf_kick_vcpu_thread(CPUState *cpu);
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 5fc8f6bb..1ba5bbf3 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -12,6 +12,8 @@
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
#include "system/runstate.h"
#include "system/hvf.h"
@@ -1218,6 +1220,44 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
assert_hvf_ok(ret);
}
+static char *hvf_get_ipa_granule(Object *obj, Error **errp)
+{
+ HVFState *s = HVF_STATE(obj);
+
+ if (s->ipa_granule == 4 * KiB) {
+ return g_strdup("4k");
+ }
+ if (s->ipa_granule == 16 * KiB) {
+ return g_strdup("16k");
+ }
+ return g_strdup("auto");
+}
+
+static void hvf_set_ipa_granule(Object *obj, const char *value, Error **errp)
+{
+ HVFState *s = HVF_STATE(obj);
+
+ if (!strcmp(value, "auto")) {
+ s->ipa_granule = 0;
+ } else if (!strcmp(value, "4k")) {
+ s->ipa_granule = 4 * KiB;
+ } else if (!strcmp(value, "16k")) {
+ s->ipa_granule = 16 * KiB;
+ } else {
+ error_setg(errp, "invalid ipa-granule: '%s' (use auto, 4k, 16k)",
+ value);
+ }
+}
+
+void hvf_arch_accel_class_init(ObjectClass *oc)
+{
+ object_class_property_add_str(oc, "ipa-granule",
+ hvf_get_ipa_granule,
+ hvf_set_ipa_granule);
+ object_class_property_set_description(oc, "ipa-granule",
+ "IPA granule for HVF stage-2 translation (auto, 4k, 16k)");
+}
+
hv_return_t hvf_arch_vm_create(MachineState *ms, uint32_t pa_range)
{
hv_return_t ret;
diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c
index c0d028b1..565c79b3 100644
--- a/target/i386/hvf/hvf.c
+++ b/target/i386/hvf/hvf.c
@@ -228,6 +228,10 @@ int hvf_arch_init(void)
return 0;
}
+void hvf_arch_accel_class_init(ObjectClass *oc)
+{
+}
+
/* 48-bit on all Intel Macs. Function currently unused. */
uint32_t hvf_arch_get_default_ipa_bit_size(void)
{
--
2.52.0
© 2016 - 2026 Red Hat, Inc.