* [PATCH] Adding support for hyper-v enlightments hv-extcalls and hv-memory-hint
@ 2026-11-02 15:58 Shmuel Fomberg
2026-11-03 10:50 ` Michael S. Tsirkin
0 siblings, 1 reply; 2+ messages in thread
From: Shmuel Fomberg @ 2026-11-02 15:58 UTC (permalink / raw)
To: qemu-devel
Cc: pbonzini, zhao1.liu, mtosatti, pierrick.bouvier, kvm,
Shmuel Fomberg
hv-memory-hint (hypercall HvExtCallMemoryHeatHint) allow a guest to
notify host which memory pages will be needed in the near future.
This is not a replacement for the balloon functionality, but can
help the kernel make better decisions when memory is scare.
Signed-off-by: Shmuel Fomberg <shmuelfomberg@gmail.com>
---
docs/system/i386/hyperv.rst | 14 +++
hw/hyperv/hyperv.c | 158 +++++++++++++++++++++++++++++++
include/hw/hyperv/hyperv-proto.h | 25 +++++
include/hw/hyperv/hyperv.h | 13 +++
include/qemu/madvise.h | 7 ++
target/i386/cpu.c | 3 +
target/i386/cpu.h | 2 +
target/i386/kvm/hyperv-proto.h | 1 +
target/i386/kvm/hyperv.c | 24 +++++
target/i386/kvm/kvm.c | 7 ++
10 files changed, 254 insertions(+)
diff --git a/docs/system/i386/hyperv.rst b/docs/system/i386/hyperv.rst
index e1babffb0f..1e164f1863 100644
--- a/docs/system/i386/hyperv.rst
+++ b/docs/system/i386/hyperv.rst
@@ -259,6 +259,15 @@ Existing enlightenments
Recommended: ``hv-evmcs`` (Intel)
+``hv-extcalls``
+ Advertises extended Hyper-V hypercalls (call codes 0x8000 and above) to
+ the guest.
+
+``hv-memory-hint``
+ Enables guest memory heat hints (HvExtCallMemoryHeatHint).
+
+ Requires: ``hv-extcalls``
+
Supplementary features
----------------------
@@ -276,6 +285,11 @@ Supplementary features
Windows guests from booting when used without proper configuration. If needed,
``hv-syndbg`` can be enabled additionally.
+ Note: ``hv-passthrough`` enables ``hv-extcalls`` when the host KVM advertises
+ it. It does not enable ``hv-memory-hint``, which is implemented in QEMU and
+ has no KVM CPUID bit. Enable ``hv-memory-hint`` explicitly when guest heat
+ hints should reach the host.
+
Note: ``hv-passthrough`` effectively prevents migration as the list of enabled
enlightenments may differ between target and destination hosts.
diff --git a/hw/hyperv/hyperv.c b/hw/hyperv/hyperv.c
index 900ff80213..b3fce7b563 100644
--- a/hw/hyperv/hyperv.c
+++ b/hw/hyperv/hyperv.c
@@ -20,7 +20,9 @@
#include "system/physmem.h"
#include "qemu/bitops.h"
#include "qemu/error-report.h"
+#include "qemu/int128.h"
#include "qemu/lockable.h"
+#include "qemu/madvise.h"
#include "qemu/queue.h"
#include "qemu/rcu.h"
#include "qemu/rcu_queue.h"
@@ -646,6 +648,162 @@ unmap:
return ret;
}
+uint16_t hyperv_hcall_ext_capabilities(uint64_t outgpa, bool fast,
+ bool memory_hint)
+{
+ uint16_t ret;
+ hwaddr len;
+ uint64_t *caps = NULL;
+
+ if (fast) {
+ return HV_STATUS_INVALID_HYPERCALL_CODE;
+ }
+ if (outgpa & (__alignof__(*caps) - 1)) {
+ return HV_STATUS_INVALID_ALIGNMENT;
+ }
+
+ len = sizeof(*caps);
+ caps = physical_memory_map(outgpa, &len, 1);
+ if (!caps || len < sizeof(*caps)) {
+ ret = HV_STATUS_INSUFFICIENT_MEMORY;
+ goto unmap;
+ }
+
+ *caps = 0;
+ if (memory_hint) {
+ *caps = HV_EXT_CAP_MEMORY_HEAT_HINT;
+ if (QEMU_MADV_COLD != QEMU_MADV_INVALID) {
+ *caps |= HV_EXT_CAP_MEMORY_HEAT_HINT_COLD;
+ }
+ }
+ ret = HV_STATUS_SUCCESS;
+
+unmap:
+ if (caps) {
+ physical_memory_unmap(caps, len, 1, ret == HV_STATUS_SUCCESS ? len : 0);
+ }
+ return ret;
+}
+
+static bool hv_decode_gpa_page_range(uint64_t raw, hwaddr *gpa, hwaddr *len)
+{
+ uint64_t pages = (raw & HV_GPA_PAGE_RANGE_ADDITIONAL_MASK) + 1;
+
+ if (raw & HV_GPA_PAGE_RANGE_LARGEPAGE) {
+ if (raw & HV_GPA_PAGE_RANGE_PAGE_SIZE_1GB) {
+ *gpa = raw & HV_GPA_PAGE_RANGE_1G_GPA_MASK;
+ *len = pages << 30;
+ } else {
+ *gpa = raw & HV_GPA_PAGE_RANGE_2M_GPA_MASK;
+ *len = pages << 21;
+ }
+ } else {
+ *gpa = raw & HV_GPA_PAGE_RANGE_4K_GPA_MASK;
+ *len = pages << 12;
+ }
+
+ return *len != 0;
+}
+
+static void hv_madvise_gpa_range(hwaddr gpa, hwaddr len, int advice)
+{
+ while (len) {
+ MemoryRegionSection section;
+ hwaddr chunk, start, skip;
+ void *host;
+
+ section = memory_region_find(get_system_memory(), gpa, len);
+ if (!section.mr) {
+ break;
+ }
+
+ start = section.offset_within_address_space;
+ chunk = int128_get64(section.size);
+
+ if (!chunk || start < gpa) {
+ memory_region_unref(section.mr);
+ break;
+ }
+
+ skip = start - gpa;
+ if (skip >= len || chunk > len - skip) {
+ memory_region_unref(section.mr);
+ break;
+ }
+
+ gpa = start + chunk;
+ len -= skip + chunk;
+
+ if (memory_region_is_ram(section.mr) &&
+ !memory_region_is_rom(section.mr) &&
+ !memory_region_is_romd(section.mr)) {
+ host = memory_region_get_ram_ptr(section.mr) +
+ section.offset_within_region;
+ qemu_madvise(host, chunk, advice);
+ }
+ memory_region_unref(section.mr);
+ }
+}
+
+uint16_t hyperv_hcall_memory_hint(uint64_t in_gpa, uint16_t rep_count,
+ bool fast)
+{
+ uint16_t ret = HV_STATUS_SUCCESS;
+ hwaddr in_len, needed;
+ struct hyperv_memory_heat_hint *in = NULL;
+ unsigned int i;
+ uint64_t heat_type;
+ int advice;
+
+ if (fast) {
+ return HV_STATUS_INVALID_HYPERCALL_CODE;
+ }
+ if (rep_count < 1 || rep_count > HV_EXT_MEMORY_HEAT_HINT_MAX_RANGES) {
+ return HV_STATUS_INVALID_HYPERCALL_INPUT;
+ }
+ if (in_gpa & (__alignof__(*in) - 1)) {
+ return HV_STATUS_INVALID_ALIGNMENT;
+ }
+
+ needed = sizeof(in->heat_type) + sizeof(in->ranges[0]) * rep_count;
+ in_len = TARGET_PAGE_SIZE;
+ in = physical_memory_map(in_gpa, &in_len, 0);
+ if (!in || in_len < needed) {
+ ret = HV_STATUS_INSUFFICIENT_MEMORY;
+ goto unmap;
+ }
+
+ heat_type = in->heat_type & 3;
+ switch (heat_type) {
+ case HV_EXTMEM_HEAT_HINT_HOT:
+ advice = QEMU_MADV_WILLNEED;
+ break;
+ case HV_EXTMEM_HEAT_HINT_COLD:
+ case HV_EXTMEM_HEAT_HINT_COLD_DISCARD:
+ advice = QEMU_MADV_COLD;
+ break;
+ default:
+ ret = HV_STATUS_INVALID_PARAMETER;
+ goto unmap;
+ }
+
+ for (i = 0; i < rep_count; i++) {
+ hwaddr gpa, len;
+ uint64_t raw = in->ranges[i];
+
+ if (!hv_decode_gpa_page_range(raw, &gpa, &len)) {
+ continue;
+ }
+ hv_madvise_gpa_range(gpa, len, advice);
+ }
+
+unmap:
+ if (in) {
+ physical_memory_unmap(in, in_len, 0, 0);
+ }
+ return ret;
+}
+
static int set_event_flag_handler(uint32_t conn_id, EventNotifier *notifier)
{
int ret;
diff --git a/include/hw/hyperv/hyperv-proto.h b/include/hw/hyperv/hyperv-proto.h
index fffc5ce342..67e6f72a90 100644
--- a/include/hw/hyperv/hyperv-proto.h
+++ b/include/hw/hyperv/hyperv-proto.h
@@ -35,7 +35,32 @@
#define HV_POST_DEBUG_DATA 0x0069
#define HV_RETRIEVE_DEBUG_DATA 0x006a
#define HV_RESET_DEBUG_SESSION 0x006b
+#define HV_EXT_CALL_QUERY_CAPABILITIES 0x8001
+#define HV_EXT_CALL_MEMORY_HEAT_HINT 0x8003
#define HV_HYPERCALL_FAST (1u << 16)
+#define HV_HYPERCALL_REP_COMP_OFFSET 32
+#define HV_HYPERCALL_REP_COMP_MASK 0xfff
+
+#define HV_EXT_CAP_MEMORY_HEAT_HINT (1ull << 1)
+#define HV_EXT_CAP_MEMORY_HEAT_HINT_COLD (1ull << 2)
+#define HV_EXT_MEMORY_HEAT_HINT_MAX_RANGES 511
+
+#define HV_EXTMEM_HEAT_HINT_COLD 0
+#define HV_EXTMEM_HEAT_HINT_HOT 1
+#define HV_EXTMEM_HEAT_HINT_COLD_DISCARD 2
+
+struct hyperv_memory_heat_hint {
+ uint64_t heat_type;
+ uint64_t ranges[HV_EXT_MEMORY_HEAT_HINT_MAX_RANGES];
+};
+
+/* HV_GPA_PAGE_RANGE in each heat-hint range qword */
+#define HV_GPA_PAGE_RANGE_ADDITIONAL_MASK 0x7ffull
+#define HV_GPA_PAGE_RANGE_LARGEPAGE (1ull << 11)
+#define HV_GPA_PAGE_RANGE_PAGE_SIZE_1GB (1ull << 12)
+#define HV_GPA_PAGE_RANGE_4K_GPA_MASK (~0xfffull)
+#define HV_GPA_PAGE_RANGE_2M_GPA_MASK (~0x1fffffull)
+#define HV_GPA_PAGE_RANGE_1G_GPA_MASK (~0x3fffffffull)
/*
* Message size
diff --git a/include/hw/hyperv/hyperv.h b/include/hw/hyperv/hyperv.h
index 23091301d0..8cccbb91ba 100644
--- a/include/hw/hyperv/hyperv.h
+++ b/include/hw/hyperv/hyperv.h
@@ -70,6 +70,19 @@ uint16_t hyperv_hcall_post_message(uint64_t param, bool fast);
* the connection as specified in @param.
*/
uint16_t hyperv_hcall_signal_event(uint64_t param, bool fast);
+/*
+ * Process HV_EXT_CALL_QUERY_CAPABILITIES hypercall: list supported ext hcall
+ * fill the capabilities bit array in @outgpa.
+ */
+uint16_t hyperv_hcall_ext_capabilities(uint64_t outgpa, bool fast,
+ bool memory_hint);
+/*
+ * Process HV_EXT_CALL_MEMORY_HEAT_HINT hypercall: receive a list of hot/cold
+ * pages and call madvice on them.
+ */
+uint16_t hyperv_hcall_memory_hint(uint64_t in_gpa, uint16_t rep_count,
+ bool fast);
+
static inline uint32_t hyperv_vp_index(CPUState *cs)
{
diff --git a/include/qemu/madvise.h b/include/qemu/madvise.h
index e155f59a0d..fd638c2254 100644
--- a/include/qemu/madvise.h
+++ b/include/qemu/madvise.h
@@ -59,6 +59,11 @@
#else
#define QEMU_MADV_POPULATE_WRITE QEMU_MADV_INVALID
#endif
+#ifdef MADV_COLD
+#define QEMU_MADV_COLD MADV_COLD
+#else
+#define QEMU_MADV_COLD QEMU_MADV_INVALID
+#endif
#elif defined(CONFIG_POSIX_MADVISE)
@@ -73,6 +78,7 @@
#define QEMU_MADV_NOHUGEPAGE QEMU_MADV_INVALID
#define QEMU_MADV_REMOVE QEMU_MADV_DONTNEED
#define QEMU_MADV_POPULATE_WRITE QEMU_MADV_INVALID
+#define QEMU_MADV_COLD QEMU_MADV_INVALID
#else /* no-op */
@@ -87,6 +93,7 @@
#define QEMU_MADV_NOHUGEPAGE QEMU_MADV_INVALID
#define QEMU_MADV_REMOVE QEMU_MADV_INVALID
#define QEMU_MADV_POPULATE_WRITE QEMU_MADV_INVALID
+#define QEMU_MADV_COLD QEMU_MADV_INVALID
#endif
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index e65eca0459..263523d052 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -10824,6 +10824,9 @@ static const Property x86_cpu_properties[] = {
HYPERV_FEAT_TLBFLUSH_EXT, 0),
DEFINE_PROP_BIT64("hv-tlbflush-direct", X86CPU, hyperv_features,
HYPERV_FEAT_TLBFLUSH_DIRECT, 0),
+ DEFINE_PROP_BIT64("hv-extcalls", X86CPU, hyperv_features,
+ HYPERV_FEAT_EXT_HYPERCALLS, 0),
+ DEFINE_PROP_BOOL("hv-memory-hint", X86CPU, hyperv_memory_hint, false),
DEFINE_PROP_ON_OFF_AUTO("hv-no-nonarch-coresharing", X86CPU,
hyperv_no_nonarch_cs, ON_OFF_AUTO_OFF),
#ifdef CONFIG_SYNDBG
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 3945e90371..3c9da61e17 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -1489,6 +1489,7 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w);
#define HYPERV_FEAT_XMM_INPUT 18
#define HYPERV_FEAT_TLBFLUSH_EXT 19
#define HYPERV_FEAT_TLBFLUSH_DIRECT 20
+#define HYPERV_FEAT_EXT_HYPERCALLS 21
#ifndef HYPERV_SPINLOCK_NEVER_NOTIFY
#define HYPERV_SPINLOCK_NEVER_NOTIFY 0xFFFFFFFF
@@ -2374,6 +2375,7 @@ struct ArchCPU {
uint32_t hyperv_spinlock_attempts;
char *hyperv_vendor;
uint64_t hyperv_features;
+ bool hyperv_memory_hint;
bool hyperv_passthrough;
OnOffAuto hyperv_no_nonarch_cs;
uint32_t hyperv_vendor_id[3];
diff --git a/target/i386/kvm/hyperv-proto.h b/target/i386/kvm/hyperv-proto.h
index a9f056f2f3..e88913fd37 100644
--- a/target/i386/kvm/hyperv-proto.h
+++ b/target/i386/kvm/hyperv-proto.h
@@ -66,6 +66,7 @@
* HV_CPUID_FEATURES.EBX bits
*/
#define HV_PARTITION_DEBUGGING_ALLOWED (1u << 12)
+#define HV_ENABLE_EXTENDED_HYPERCALLS (1u << 20)
/*
* HV_CPUID_ENLIGHTMENT_INFO.EAX bits
diff --git a/target/i386/kvm/hyperv.c b/target/i386/kvm/hyperv.c
index 420c76b5ff..9d97f5cb4e 100644
--- a/target/i386/kvm/hyperv.c
+++ b/target/i386/kvm/hyperv.c
@@ -97,6 +97,13 @@ int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
bool fast = exit->u.hcall.input & HV_HYPERCALL_FAST;
uint64_t in_param = exit->u.hcall.params[0];
uint64_t out_param = exit->u.hcall.params[1];
+ uint64_t rep_count = (exit->u.hcall.input >> 32) & 0xFFF;
+
+ if ((code & 0x8000) &&
+ !hyperv_feat_enabled(cpu, HYPERV_FEAT_EXT_HYPERCALLS)) {
+ exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
+ return 0;
+ }
switch (code) {
case HV_POST_MESSAGE:
@@ -117,6 +124,23 @@ int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
exit->u.hcall.result =
hyperv_hcall_reset_dbg_session(out_param);
break;
+ case HV_EXT_CALL_QUERY_CAPABILITIES:
+ exit->u.hcall.result = hyperv_hcall_ext_capabilities(
+ out_param, fast, cpu->hyperv_memory_hint);
+ break;
+ case HV_EXT_CALL_MEMORY_HEAT_HINT:
+ if (!cpu->hyperv_memory_hint) {
+ exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
+ break;
+ }
+ exit->u.hcall.result =
+ hyperv_hcall_memory_hint(in_param, rep_count, fast);
+ if (exit->u.hcall.result == HV_STATUS_SUCCESS) {
+ exit->u.hcall.result |=
+ (rep_count & HV_HYPERCALL_REP_COMP_MASK) <<
+ HV_HYPERCALL_REP_COMP_OFFSET;
+ }
+ break;
default:
exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
}
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 072da8b9f1..46c983f963 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -1137,6 +1137,13 @@ static struct {
},
.dependencies = BIT(HYPERV_FEAT_VAPIC)
},
+ [HYPERV_FEAT_EXT_HYPERCALLS] = {
+ .desc = "enable extended hypercalls (hv-extcalls)",
+ .flags = {
+ {.func = HV_CPUID_FEATURES, .reg = R_EBX,
+ .bits = HV_ENABLE_EXTENDED_HYPERCALLS}
+ }
+ },
};
static struct kvm_cpuid2 *try_get_hv_cpuid(CPUState *cs, int max,
--
2.54.0 (Apple Git-157)
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] Adding support for hyper-v enlightments hv-extcalls and hv-memory-hint
2026-11-02 15:58 [PATCH] Adding support for hyper-v enlightments hv-extcalls and hv-memory-hint Shmuel Fomberg
@ 2026-11-03 10:50 ` Michael S. Tsirkin
0 siblings, 0 replies; 2+ messages in thread
From: Michael S. Tsirkin @ 2026-11-03 10:50 UTC (permalink / raw)
To: Shmuel Fomberg
Cc: qemu-devel, pbonzini, zhao1.liu, mtosatti, pierrick.bouvier, kvm
Thanks for the patch! yet something to improve:
On Mon, Nov 02, 2026 at 05:58:04PM +0200, Shmuel Fomberg wrote:
> hv-memory-hint (hypercall HvExtCallMemoryHeatHint) allow a guest to
> notify host which memory pages will be needed in the near future.
> This is not a replacement for the balloon functionality, but can
> help the kernel
given this is supposed to be an optimization, I expect to see
some performance numbers showing it actually helps, and some
documentation explaining when it does and when it doesn't.
e.g. does it work with vhost-user (shmem)? hugetlb?
pls document which configs were tested, with linux and windows guests.
> make better decisions when memory is scare.
scarse?
>
> Signed-off-by: Shmuel Fomberg <shmuelfomberg@gmail.com>
> ---
> docs/system/i386/hyperv.rst | 14 +++
> hw/hyperv/hyperv.c | 158 +++++++++++++++++++++++++++++++
> include/hw/hyperv/hyperv-proto.h | 25 +++++
> include/hw/hyperv/hyperv.h | 13 +++
> include/qemu/madvise.h | 7 ++
> target/i386/cpu.c | 3 +
> target/i386/cpu.h | 2 +
> target/i386/kvm/hyperv-proto.h | 1 +
> target/i386/kvm/hyperv.c | 24 +++++
> target/i386/kvm/kvm.c | 7 ++
> 10 files changed, 254 insertions(+)
>
> diff --git a/docs/system/i386/hyperv.rst b/docs/system/i386/hyperv.rst
> index e1babffb0f..1e164f1863 100644
> --- a/docs/system/i386/hyperv.rst
> +++ b/docs/system/i386/hyperv.rst
> @@ -259,6 +259,15 @@ Existing enlightenments
>
> Recommended: ``hv-evmcs`` (Intel)
>
> +``hv-extcalls``
> + Advertises extended Hyper-V hypercalls (call codes 0x8000 and above) to
> + the guest.
> +
> +``hv-memory-hint``
> + Enables guest memory heat hints (HvExtCallMemoryHeatHint).
> +
> + Requires: ``hv-extcalls``
> +
> Supplementary features
> ----------------------
>
> @@ -276,6 +285,11 @@ Supplementary features
> Windows guests from booting when used without proper configuration. If needed,
> ``hv-syndbg`` can be enabled additionally.
>
> + Note: ``hv-passthrough`` enables ``hv-extcalls`` when the host KVM advertises
> + it. It does not enable ``hv-memory-hint``, which is implemented in QEMU and
> + has no KVM CPUID bit. Enable ``hv-memory-hint`` explicitly when guest heat
> + hints should reach the host.
> +
I'm not sure what all this means.
"KVM CPUID" is also obscure.
Can you pls rewrite? Maybe along the lines of:
if .... and .... then ... results in ...
if .... and .... then ... is ignored
> Note: ``hv-passthrough`` effectively prevents migration as the list of enabled
> enlightenments may differ between target and destination hosts.
>
> diff --git a/hw/hyperv/hyperv.c b/hw/hyperv/hyperv.c
> index 900ff80213..b3fce7b563 100644
> --- a/hw/hyperv/hyperv.c
> +++ b/hw/hyperv/hyperv.c
> @@ -20,7 +20,9 @@
> #include "system/physmem.h"
> #include "qemu/bitops.h"
> #include "qemu/error-report.h"
> +#include "qemu/int128.h"
> #include "qemu/lockable.h"
> +#include "qemu/madvise.h"
> #include "qemu/queue.h"
> #include "qemu/rcu.h"
> #include "qemu/rcu_queue.h"
> @@ -646,6 +648,162 @@ unmap:
> return ret;
> }
>
> +uint16_t hyperv_hcall_ext_capabilities(uint64_t outgpa, bool fast,
> + bool memory_hint)
> +{
> + uint16_t ret;
> + hwaddr len;
> + uint64_t *caps = NULL;
> +
> + if (fast) {
> + return HV_STATUS_INVALID_HYPERCALL_CODE;
> + }
> + if (outgpa & (__alignof__(*caps) - 1)) {
> + return HV_STATUS_INVALID_ALIGNMENT;
> + }
> +
> + len = sizeof(*caps);
> + caps = physical_memory_map(outgpa, &len, 1);
> + if (!caps || len < sizeof(*caps)) {
> + ret = HV_STATUS_INSUFFICIENT_MEMORY;
> + goto unmap;
> + }
> +
> + *caps = 0;
> + if (memory_hint) {
> + *caps = HV_EXT_CAP_MEMORY_HEAT_HINT;
> + if (QEMU_MADV_COLD != QEMU_MADV_INVALID) {
> + *caps |= HV_EXT_CAP_MEMORY_HEAT_HINT_COLD;
> + }
> + }
> + ret = HV_STATUS_SUCCESS;
> +
> +unmap:
> + if (caps) {
> + physical_memory_unmap(caps, len, 1, ret == HV_STATUS_SUCCESS ? len : 0);
> + }
> + return ret;
> +}
> +
> +static bool hv_decode_gpa_page_range(uint64_t raw, hwaddr *gpa, hwaddr *len)
> +{
> + uint64_t pages = (raw & HV_GPA_PAGE_RANGE_ADDITIONAL_MASK) + 1;
shouldn't we check alignment and error out rather than force it?
> +
> + if (raw & HV_GPA_PAGE_RANGE_LARGEPAGE) {
> + if (raw & HV_GPA_PAGE_RANGE_PAGE_SIZE_1GB) {
> + *gpa = raw & HV_GPA_PAGE_RANGE_1G_GPA_MASK;
> + *len = pages << 30;
> + } else {
> + *gpa = raw & HV_GPA_PAGE_RANGE_2M_GPA_MASK;
> + *len = pages << 21;
> + }
> + } else {
> + *gpa = raw & HV_GPA_PAGE_RANGE_4K_GPA_MASK;
> + *len = pages << 12;
> + }
> +
> + return *len != 0;
how can len be 0?
> +}
> +
> +static void hv_madvise_gpa_range(hwaddr gpa, hwaddr len, int advice)
> +{
> + while (len) {
> + MemoryRegionSection section;
> + hwaddr chunk, start, skip;
> + void *host;
> +
> + section = memory_region_find(get_system_memory(), gpa, len);
> + if (!section.mr) {
> + break;
why break? we could have an unmapped hole followed by a mapped one.
> + }
> +
> + start = section.offset_within_address_space;
> + chunk = int128_get64(section.size);
> +
> + if (!chunk || start < gpa) {
is this dead code? we got the mr from find can start be before?
> + memory_region_unref(section.mr);
> + break;
> + }
> +
> + skip = start - gpa;
> + if (skip >= len || chunk > len - skip) {
can this trigger? or more dead code?
> + memory_region_unref(section.mr);
> + break;
> + }
> +
> + gpa = start + chunk;
> + len -= skip + chunk;
> +
> + if (memory_region_is_ram(section.mr) &&
> + !memory_region_is_rom(section.mr) &&
> + !memory_region_is_romd(section.mr)) {
> + host = memory_region_get_ram_ptr(section.mr) +
> + section.offset_within_region;
> + qemu_madvise(host, chunk, advice);
> + }
> + memory_region_unref(section.mr);
> + }
> +}
> +
> +uint16_t hyperv_hcall_memory_hint(uint64_t in_gpa, uint16_t rep_count,
> + bool fast)
> +{
> + uint16_t ret = HV_STATUS_SUCCESS;
> + hwaddr in_len, needed;
> + struct hyperv_memory_heat_hint *in = NULL;
> + unsigned int i;
> + uint64_t heat_type;
> + int advice;
> +
> + if (fast) {
> + return HV_STATUS_INVALID_HYPERCALL_CODE;
> + }
> + if (rep_count < 1 || rep_count > HV_EXT_MEMORY_HEAT_HINT_MAX_RANGES) {
> + return HV_STATUS_INVALID_HYPERCALL_INPUT;
> + }
> + if (in_gpa & (__alignof__(*in) - 1)) {
> + return HV_STATUS_INVALID_ALIGNMENT;
> + }
> +
> + needed = sizeof(in->heat_type) + sizeof(in->ranges[0]) * rep_count;
> + in_len = TARGET_PAGE_SIZE;
> + in = physical_memory_map(in_gpa, &in_len, 0);
> + if (!in || in_len < needed) {
> + ret = HV_STATUS_INSUFFICIENT_MEMORY;
> + goto unmap;
needed can be 4096 with rep_count 511, can it not? if the input
is not page aligned or end of page is not ram, will this not fail?
why not just map needed bytes?
> + }
> +
> + heat_type = in->heat_type & 3;
I would add
#define HV_EXTMEM_HEAT_HINT_MASK 3
or
(HV_EXTMEM_HEAT_HINT_HOT|HV_EXTMEM_HEAT_HINT_COLD|HV_EXTMEM_HEAT_HINT_COLD_DISCARD)
also are you really supposed to just ignore rest of bits?
add a code comment explaining why.
> + switch (heat_type) {
> + case HV_EXTMEM_HEAT_HINT_HOT:
> + advice = QEMU_MADV_WILLNEED;
> + break;
> + case HV_EXTMEM_HEAT_HINT_COLD:
> + case HV_EXTMEM_HEAT_HINT_COLD_DISCARD:
> + advice = QEMU_MADV_COLD;
> + break;
> + default:
> + ret = HV_STATUS_INVALID_PARAMETER;
> + goto unmap;
> + }
Pls document what are these things supposed to do, with code comments.
For example "discard" sounds like MADV_DONTNEED, MADV_REMOVE or
MADV_FREE or a combination of these to me?
"COLD" sounds kinda like MADV_COLD but there's also MADV_PAGEOUT
which is same but more aggressive.
> +
> + for (i = 0; i < rep_count; i++) {
> + hwaddr gpa, len;
> + uint64_t raw = in->ranges[i];
> +
> + if (!hv_decode_gpa_page_range(raw, &gpa, &len)) {
> + continue;
what's the point of this? does it ever trigger?
> + }
> + hv_madvise_gpa_range(gpa, len, advice);
> + }
> +
> +unmap:
> + if (in) {
> + physical_memory_unmap(in, in_len, 0, 0);
> + }
> + return ret;
> +}
> +
> static int set_event_flag_handler(uint32_t conn_id, EventNotifier *notifier)
> {
> int ret;
> diff --git a/include/hw/hyperv/hyperv-proto.h b/include/hw/hyperv/hyperv-proto.h
> index fffc5ce342..67e6f72a90 100644
> --- a/include/hw/hyperv/hyperv-proto.h
> +++ b/include/hw/hyperv/hyperv-proto.h
> @@ -35,7 +35,32 @@
> #define HV_POST_DEBUG_DATA 0x0069
> #define HV_RETRIEVE_DEBUG_DATA 0x006a
> #define HV_RESET_DEBUG_SESSION 0x006b
> +#define HV_EXT_CALL_QUERY_CAPABILITIES 0x8001
> +#define HV_EXT_CALL_MEMORY_HEAT_HINT 0x8003
> #define HV_HYPERCALL_FAST (1u << 16)
> +#define HV_HYPERCALL_REP_COMP_OFFSET 32
> +#define HV_HYPERCALL_REP_COMP_MASK 0xfff
> +
> +#define HV_EXT_CAP_MEMORY_HEAT_HINT (1ull << 1)
> +#define HV_EXT_CAP_MEMORY_HEAT_HINT_COLD (1ull << 2)
Here:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/include/hyperv/hvgdk_ext.h
I see:
#define HV_EXT_CAPABILITY_MEMORY_COLD_DISCARD_HINT BIT(8)
> +#define HV_EXT_MEMORY_HEAT_HINT_MAX_RANGES 511
> +
> +#define HV_EXTMEM_HEAT_HINT_COLD 0
> +#define HV_EXTMEM_HEAT_HINT_HOT 1
> +#define HV_EXTMEM_HEAT_HINT_COLD_DISCARD 2
> +
> +struct hyperv_memory_heat_hint {
> + uint64_t heat_type;
> + uint64_t ranges[HV_EXT_MEMORY_HEAT_HINT_MAX_RANGES];
> +};
> +
> +/* HV_GPA_PAGE_RANGE in each heat-hint range qword */
> +#define HV_GPA_PAGE_RANGE_ADDITIONAL_MASK 0x7ffull
> +#define HV_GPA_PAGE_RANGE_LARGEPAGE (1ull << 11)
> +#define HV_GPA_PAGE_RANGE_PAGE_SIZE_1GB (1ull << 12)
> +#define HV_GPA_PAGE_RANGE_4K_GPA_MASK (~0xfffull)
> +#define HV_GPA_PAGE_RANGE_2M_GPA_MASK (~0x1fffffull)
> +#define HV_GPA_PAGE_RANGE_1G_GPA_MASK (~0x3fffffffull)
>
Can you try to move the linux bits out to a uapi file and import from there,
instead of duplicating?
> /*
> * Message size
> diff --git a/include/hw/hyperv/hyperv.h b/include/hw/hyperv/hyperv.h
> index 23091301d0..8cccbb91ba 100644
> --- a/include/hw/hyperv/hyperv.h
> +++ b/include/hw/hyperv/hyperv.h
> @@ -70,6 +70,19 @@ uint16_t hyperv_hcall_post_message(uint64_t param, bool fast);
> * the connection as specified in @param.
> */
> uint16_t hyperv_hcall_signal_event(uint64_t param, bool fast);
> +/*
> + * Process HV_EXT_CALL_QUERY_CAPABILITIES hypercall: list supported ext hcall
> + * fill the capabilities bit array in @outgpa.
> + */
> +uint16_t hyperv_hcall_ext_capabilities(uint64_t outgpa, bool fast,
> + bool memory_hint);
> +/*
> + * Process HV_EXT_CALL_MEMORY_HEAT_HINT hypercall: receive a list of hot/cold
> + * pages and call madvice on them.
madvise
> + */
> +uint16_t hyperv_hcall_memory_hint(uint64_t in_gpa, uint16_t rep_count,
> + bool fast);
> +
>
> static inline uint32_t hyperv_vp_index(CPUState *cs)
> {
> diff --git a/include/qemu/madvise.h b/include/qemu/madvise.h
> index e155f59a0d..fd638c2254 100644
> --- a/include/qemu/madvise.h
> +++ b/include/qemu/madvise.h
> @@ -59,6 +59,11 @@
> #else
> #define QEMU_MADV_POPULATE_WRITE QEMU_MADV_INVALID
> #endif
> +#ifdef MADV_COLD
> +#define QEMU_MADV_COLD MADV_COLD
> +#else
> +#define QEMU_MADV_COLD QEMU_MADV_INVALID
> +#endif
>
> #elif defined(CONFIG_POSIX_MADVISE)
>
> @@ -73,6 +78,7 @@
> #define QEMU_MADV_NOHUGEPAGE QEMU_MADV_INVALID
> #define QEMU_MADV_REMOVE QEMU_MADV_DONTNEED
> #define QEMU_MADV_POPULATE_WRITE QEMU_MADV_INVALID
> +#define QEMU_MADV_COLD QEMU_MADV_INVALID
>
> #else /* no-op */
>
> @@ -87,6 +93,7 @@
> #define QEMU_MADV_NOHUGEPAGE QEMU_MADV_INVALID
> #define QEMU_MADV_REMOVE QEMU_MADV_INVALID
> #define QEMU_MADV_POPULATE_WRITE QEMU_MADV_INVALID
> +#define QEMU_MADV_COLD QEMU_MADV_INVALID
>
> #endif
>
> diff --git a/target/i386/cpu.c b/target/i386/cpu.c
> index e65eca0459..263523d052 100644
> --- a/target/i386/cpu.c
> +++ b/target/i386/cpu.c
> @@ -10824,6 +10824,9 @@ static const Property x86_cpu_properties[] = {
> HYPERV_FEAT_TLBFLUSH_EXT, 0),
> DEFINE_PROP_BIT64("hv-tlbflush-direct", X86CPU, hyperv_features,
> HYPERV_FEAT_TLBFLUSH_DIRECT, 0),
> + DEFINE_PROP_BIT64("hv-extcalls", X86CPU, hyperv_features,
> + HYPERV_FEAT_EXT_HYPERCALLS, 0),
> + DEFINE_PROP_BOOL("hv-memory-hint", X86CPU, hyperv_memory_hint, false),
> DEFINE_PROP_ON_OFF_AUTO("hv-no-nonarch-coresharing", X86CPU,
> hyperv_no_nonarch_cs, ON_OFF_AUTO_OFF),
> #ifdef CONFIG_SYNDBG
> diff --git a/target/i386/cpu.h b/target/i386/cpu.h
> index 3945e90371..3c9da61e17 100644
> --- a/target/i386/cpu.h
> +++ b/target/i386/cpu.h
> @@ -1489,6 +1489,7 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w);
> #define HYPERV_FEAT_XMM_INPUT 18
> #define HYPERV_FEAT_TLBFLUSH_EXT 19
> #define HYPERV_FEAT_TLBFLUSH_DIRECT 20
> +#define HYPERV_FEAT_EXT_HYPERCALLS 21
>
> #ifndef HYPERV_SPINLOCK_NEVER_NOTIFY
> #define HYPERV_SPINLOCK_NEVER_NOTIFY 0xFFFFFFFF
> @@ -2374,6 +2375,7 @@ struct ArchCPU {
> uint32_t hyperv_spinlock_attempts;
> char *hyperv_vendor;
> uint64_t hyperv_features;
> + bool hyperv_memory_hint;
> bool hyperv_passthrough;
> OnOffAuto hyperv_no_nonarch_cs;
> uint32_t hyperv_vendor_id[3];
> diff --git a/target/i386/kvm/hyperv-proto.h b/target/i386/kvm/hyperv-proto.h
> index a9f056f2f3..e88913fd37 100644
> --- a/target/i386/kvm/hyperv-proto.h
> +++ b/target/i386/kvm/hyperv-proto.h
> @@ -66,6 +66,7 @@
> * HV_CPUID_FEATURES.EBX bits
> */
> #define HV_PARTITION_DEBUGGING_ALLOWED (1u << 12)
> +#define HV_ENABLE_EXTENDED_HYPERCALLS (1u << 20)
>
> /*
> * HV_CPUID_ENLIGHTMENT_INFO.EAX bits
> diff --git a/target/i386/kvm/hyperv.c b/target/i386/kvm/hyperv.c
> index 420c76b5ff..9d97f5cb4e 100644
> --- a/target/i386/kvm/hyperv.c
> +++ b/target/i386/kvm/hyperv.c
> @@ -97,6 +97,13 @@ int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
> bool fast = exit->u.hcall.input & HV_HYPERCALL_FAST;
> uint64_t in_param = exit->u.hcall.params[0];
> uint64_t out_param = exit->u.hcall.params[1];
> + uint64_t rep_count = (exit->u.hcall.input >> 32) & 0xFFF;
> +
> + if ((code & 0x8000) &&
> + !hyperv_feat_enabled(cpu, HYPERV_FEAT_EXT_HYPERCALLS)) {
> + exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
> + return 0;
> + }
>
> switch (code) {
> case HV_POST_MESSAGE:
> @@ -117,6 +124,23 @@ int kvm_hv_handle_exit(X86CPU *cpu, struct kvm_hyperv_exit *exit)
> exit->u.hcall.result =
> hyperv_hcall_reset_dbg_session(out_param);
> break;
> + case HV_EXT_CALL_QUERY_CAPABILITIES:
> + exit->u.hcall.result = hyperv_hcall_ext_capabilities(
> + out_param, fast, cpu->hyperv_memory_hint);
> + break;
> + case HV_EXT_CALL_MEMORY_HEAT_HINT:
> + if (!cpu->hyperv_memory_hint) {
> + exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
> + break;
> + }
> + exit->u.hcall.result =
> + hyperv_hcall_memory_hint(in_param, rep_count, fast);
> + if (exit->u.hcall.result == HV_STATUS_SUCCESS) {
> + exit->u.hcall.result |=
> + (rep_count & HV_HYPERCALL_REP_COMP_MASK) <<
> + HV_HYPERCALL_REP_COMP_OFFSET;
> + }
> + break;
> default:
> exit->u.hcall.result = HV_STATUS_INVALID_HYPERCALL_CODE;
> }
> diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
> index 072da8b9f1..46c983f963 100644
> --- a/target/i386/kvm/kvm.c
> +++ b/target/i386/kvm/kvm.c
> @@ -1137,6 +1137,13 @@ static struct {
> },
> .dependencies = BIT(HYPERV_FEAT_VAPIC)
> },
> + [HYPERV_FEAT_EXT_HYPERCALLS] = {
> + .desc = "enable extended hypercalls (hv-extcalls)",
> + .flags = {
> + {.func = HV_CPUID_FEATURES, .reg = R_EBX,
> + .bits = HV_ENABLE_EXTENDED_HYPERCALLS}
> + }
> + },
> };
>
> static struct kvm_cpuid2 *try_get_hv_cpuid(CPUState *cs, int max,
> --
> 2.54.0 (Apple Git-157)
>
>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-03 10:50 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 15:58 [PATCH] Adding support for hyper-v enlightments hv-extcalls and hv-memory-hint Shmuel Fomberg
2026-11-03 10:50 ` Michael S. Tsirkin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox