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 <[email protected]>
---
 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)


Reply via email to