* Re: [PATCH v6 3/3] hyperv: Cleanly shutdown root partition with MSHV
From: Praveen K Paladugu @ 2025-12-05 19:59 UTC (permalink / raw)
To: Stanislav Kinsburskii, Praveen K Paladugu
Cc: kys, haiyangz, wei.liu, decui, tglx, mingo, linux-hyperv,
linux-kernel, bp, dave.hansen, x86, hpa, arnd, anbelski,
easwar.hariharan, nunodasneves
In-Reply-To: <aS3K898vD31Qi8mE@skinsburskii.localdomain>
On 12/1/2025 11:05 AM, Stanislav Kinsburskii wrote:
> On Wed, Nov 26, 2025 at 03:49:53PM -0600, Praveen K Paladugu wrote:
>> When a root partition running on MSHV is powered off, the default
>> behavior is to write ACPI registers to power-off. However, this ACPI
>> write is intercepted by MSHV and will result in a Machine Check
>> Exception(MCE).
>>
>> The root partition eventually panics with a trace similar to:
>>
>> [ 81.306348] reboot: Power down
>> [ 81.314709] mce: [Hardware Error]: CPU 0: Machine Check Exception: 4 Bank 0: b2000000c0060001
>> [ 81.314711] mce: [Hardware Error]: TSC 3b8cb60a66 PPIN 11d98332458e4ea9
>> [ 81.314713] mce: [Hardware Error]: PROCESSOR 0:606a6 TIME 1759339405 SOCKET 0 APIC 0 microcode ffffffff
>> [ 81.314715] mce: [Hardware Error]: Run the above through 'mcelog --ascii'
>> [ 81.314716] mce: [Hardware Error]: Machine check: Processor context corrupt
>> [ 81.314717] Kernel panic - not syncing: Fatal machine check
>>
>> To correctly shutdown a root partition running on MSHV hypervisor, sleep
>> state information must be configured within the hypervsior. Later, the
>> HVCALL_ENTER_SLEEP_STATE hypercall should be invoked as the last step in
>> the shutdown sequence.
>>
>> The previous patch configures the sleep state information and this patch
>> invokes HVCALL_ENTER_SLEEP_STATE hypercall to cleanly shutdown the root
>> partition.
>>
>> Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
>> Co-developed-by: Anatol Belski <anbelski@linux.microsoft.com>
>> Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
>> Reviewed-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
>> ---
>> arch/x86/include/asm/mshyperv.h | 2 ++
>> arch/x86/kernel/cpu/mshyperv.c | 2 ++
>> drivers/hv/mshv_common.c | 18 ++++++++++++++++++
>> 3 files changed, 22 insertions(+)
>>
>> diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
>> index 166053df0484..4c22f3257368 100644
>> --- a/arch/x86/include/asm/mshyperv.h
>> +++ b/arch/x86/include/asm/mshyperv.h
>> @@ -183,9 +183,11 @@ void hv_apic_init(void);
>> void __init hv_init_spinlocks(void);
>> bool hv_vcpu_is_preempted(int vcpu);
>> void hv_sleep_notifiers_register(void);
>> +void hv_machine_power_off(void);
>> #else
>> static inline void hv_apic_init(void) {}
>> static inline void hv_sleep_notifiers_register(void) {};
>> +static inline void hv_machine_power_off(void) {};
>> #endif
>>
>> struct irq_domain *hv_create_pci_msi_domain(void);
>> diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
>> index fac9953a72ef..579fb2c64cfd 100644
>> --- a/arch/x86/kernel/cpu/mshyperv.c
>> +++ b/arch/x86/kernel/cpu/mshyperv.c
>> @@ -621,6 +621,8 @@ static void __init ms_hyperv_init_platform(void)
>> #endif
>>
>> #if IS_ENABLED(CONFIG_HYPERV)
>> + if (hv_root_partition())
>> + machine_ops.power_off = hv_machine_power_off;
>> #if defined(CONFIG_KEXEC_CORE)
>> machine_ops.shutdown = hv_machine_shutdown;
>> #endif
>> diff --git a/drivers/hv/mshv_common.c b/drivers/hv/mshv_common.c
>> index f1d4e81107ee..28905e3ed9c0 100644
>> --- a/drivers/hv/mshv_common.c
>> +++ b/drivers/hv/mshv_common.c
>> @@ -217,4 +217,22 @@ void hv_sleep_notifiers_register(void)
>> pr_err("%s: cannot register reboot notifier %d\n", __func__,
>> ret);
>> }
>> +
>> +/*
>> + * Power off the machine by entering S5 sleep state via Hyper-V hypercall.
>> + * This call does not return if successful.
>> + */
>> +void hv_machine_power_off(void)
>> +{
>> + unsigned long flags;
>> + struct hv_input_enter_sleep_state *in;
>> +
>> + local_irq_save(flags);
>> + in = *this_cpu_ptr(hyperv_pcpu_input_arg);
>> + in->sleep_state = HV_SLEEP_STATE_S5;
>> +
>> + (void)hv_do_hypercall(HVCALL_ENTER_SLEEP_STATE, in, NULL);
>
> Should this the error be printed?
>
> Acked-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
I will add a BUG() here, as the hypercall is not supposed to return.
>> + local_irq_restore(flags);
>> +
>> +}
>> #endif
>> --
>> 2.51.0
>
--
Regards,
Praveen K Paladugu
^ permalink raw reply
* Re: [PATCH v6 0/3] Add support for clean shutdown with MSHV
From: Praveen K Paladugu @ 2025-12-05 19:58 UTC (permalink / raw)
To: Michael Kelley, Praveen K Paladugu, kys@microsoft.com,
haiyangz@microsoft.com, wei.liu@kernel.org, decui@microsoft.com,
tglx@linutronix.de, mingo@redhat.com,
linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org,
bp@alien8.de, dave.hansen@linux.intel.com, x86@kernel.org,
hpa@zytor.com, arnd@arndb.de
Cc: anbelski@linux.microsoft.com,
easwar.hariharan@linux.microsoft.com,
nunodasneves@linux.microsoft.com,
skinsburskii@linux.microsoft.com
In-Reply-To: <SN6PR02MB415792702B78B9855485DCE5D4DDA@SN6PR02MB4157.namprd02.prod.outlook.com>
On 11/29/2025 12:48 AM, Michael Kelley wrote:
> From: Praveen K Paladugu <prapal@linux.microsoft.com> Sent: Wednesday, November 26, 2025 1:50 PM
>>
>> Add support for clean shutdown of the root partition when running on
>> MSHV Hypervisor.
>>
>> v6:
>> - Fixed build errors, by adding CONFIG_X86_64 guard
>
> Adding the CONFIG_X86_64 guard seems like the right solution, and it does
> make the build errors go away. However note that as coded in drivers/hv/Makefile,
> the code under the new guard won't be built at all unless CONFIG_MSHV_ROOT
> is set (ignoring the VTL case for now), which can only happen in the X86_64 or
> ARM64 cases. So it was nagging at me as to why the guard is needed for an
> x86 32-bit build failure reported by the kernel test robot.
>
> It turns out there's an underlying bug in drivers/hv/Makefile causing
> mshv_common.o to be built in cases when it shouldn't be, such as the x86
> 32-bit case. The build failures reported by the kernel test robot were on these
> cases when it shouldn't be built in the first place. The bug is in this Makefile line:
>
> ifneq ($(CONFIG_MSHV_ROOT) $(CONFIG_MSHV_VTL),)
>
> which should be
>
> ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL),)
>
> The buggy version has a spurious "space" character before the start of
> $(CONFIG_MSHV_VTL) such that the result is always "not equal" and
> mshv_common.o is always built.
>
> If the Makefile is fixed, then the X86_64 guards you added in
> mshv_common.c are not needed. Furthermore, the stubs for
> hv_sleep_notifiers_register() and hv_machine_power_off() in
> arch/x86/include/asm/mshyperv.h for the !CONFIG_X86_64 case aren't
> needed. And the declarations for hv_sleep_notifiers_register() and
> hv_machine_power_off() can be moved out from under the #ifdef
> CONFIG_X86_64. The bottom line is that nothing in this patch set needs
> to be guarded by CONFIG_X86_64.
>
Thanks for this investigation Michael. With these changes, I am not able
to reproduce any build failures anymore. I will push these changes in v7.
Praveen
> Here's a quick diff of what I changed on top of your v6 patch set
> (including the fix to drivers/hv/Makefile). I tested the build process
> on both x86/64 and arm64, with and without CONFIG_MSHV_ROOT
> selected.
>
> diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
> index 4c22f3257368..01d192e70211 100644
> --- a/arch/x86/include/asm/mshyperv.h
> +++ b/arch/x86/include/asm/mshyperv.h
> @@ -178,16 +178,15 @@ int hyperv_fill_flush_guest_mapping_list(
> struct hv_guest_mapping_flush_list *flush,
> u64 start_gfn, u64 end_gfn);
>
> +void hv_sleep_notifiers_register(void);
> +void hv_machine_power_off(void);
> +
> #ifdef CONFIG_X86_64
> void hv_apic_init(void);
> void __init hv_init_spinlocks(void);
> bool hv_vcpu_is_preempted(int vcpu);
> -void hv_sleep_notifiers_register(void);
> -void hv_machine_power_off(void);
> #else
> static inline void hv_apic_init(void) {}
> -static inline void hv_sleep_notifiers_register(void) {};
> -static inline void hv_machine_power_off(void) {};
> #endif
>
> struct irq_domain *hv_create_pci_msi_domain(void);
> diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
> index 58b8d07639f3..6d929fb0e13d 100644
> --- a/drivers/hv/Makefile
> +++ b/drivers/hv/Makefile
> @@ -20,6 +20,6 @@ mshv_vtl-y := mshv_vtl_main.o
> # Code that must be built-in
> obj-$(CONFIG_HYPERV) += hv_common.o
> obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o
> -ifneq ($(CONFIG_MSHV_ROOT) $(CONFIG_MSHV_VTL),)
> +ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL),)
> obj-y += mshv_common.o
> endif
> diff --git a/drivers/hv/mshv_common.c b/drivers/hv/mshv_common.c
> index 28905e3ed9c0..73505cbdc324 100644
> --- a/drivers/hv/mshv_common.c
> +++ b/drivers/hv/mshv_common.c
> @@ -142,7 +142,6 @@ int hv_call_get_partition_property(u64 partition_id,
> }
> EXPORT_SYMBOL_GPL(hv_call_get_partition_property);
>
> -#ifdef CONFIG_X86_64
> /*
> * Corresponding sleep states have to be initialized in order for a subsequent
> * HVCALL_ENTER_SLEEP_STATE call to succeed. Currently only S5 state as per
> @@ -235,4 +234,3 @@ void hv_machine_power_off(void)
> local_irq_restore(flags);
>
> }
> -#endif
>
> The Makefile fix needs to be a separate patch.
>
> I think I got all this correct, but please double-check my work! :-)
>
> Michael
>
>> - Moved machine_ops hook definition to ms_hyperv_init_platform
>> - Addressed review comments in v5
>>
>> v5:
>> - Fixed build errors
>> - Padded struct hv_input_set_system_property for alignment
>> - Dropped CONFIG_ACPI stub
>>
>> v4:
>> - Adopted machine_ops to order invoking HV_ENTER_SLEEP_STATE as the
>> last step in shutdown sequence.
>> - This ensures rest of the cleanups are done before powering off
>>
>> v3:
>> - Dropped acpi_sleep handlers as they are not used on mshv
>> - Applied ordering for hv_reboot_notifier
>> - Fixed build issues on i386, arm64 architectures
>>
>> v2:
>> - Addressed review comments from v1.
>> - Moved all sleep state handling methods under CONFIG_ACPI stub
>> - - This fixes build issues on non-x86 architectures.
>>
>>
>> Praveen K Paladugu (3):
>> hyperv: Add definitions for MSHV sleep state configuration
>> hyperv: Use reboot notifier to configure sleep state
>> hyperv: Cleanly shutdown root partition with MSHV
>>
>> arch/x86/hyperv/hv_init.c | 1 +
>> arch/x86/include/asm/mshyperv.h | 4 ++
>> arch/x86/kernel/cpu/mshyperv.c | 2 +
>> drivers/hv/mshv_common.c | 98 +++++++++++++++++++++++++++++++++
>> include/hyperv/hvgdk_mini.h | 4 +-
>> include/hyperv/hvhdk_mini.h | 40 ++++++++++++++
>> 6 files changed, 148 insertions(+), 1 deletion(-)
>>
>> --
>> 2.51.0
>>
>
>
--
Regards,
Praveen K Paladugu
^ permalink raw reply
* [PATCH v2 3/3] mshv: Add debugfs to view hypervisor statistics
From: Nuno Das Neves @ 2025-12-05 18:58 UTC (permalink / raw)
To: linux-hyperv, linux-kernel, skinsburskii
Cc: kys, haiyangz, wei.liu, decui, longli, mhklinux, prapal, mrathor,
paekkaladevi, Nuno Das Neves, Jinank Jain
In-Reply-To: <1764961122-31679-1-git-send-email-nunodasneves@linux.microsoft.com>
Introduce a debugfs interface to expose root and child partition stats
when running with mshv_root.
Create a debugfs directory "mshv" containing 'stats' files organized by
type and id. A stats file contains a number of counters depending on
its type. e.g. an excerpt from a VP stats file:
TotalRunTime : 1997602722
HypervisorRunTime : 649671371
RemoteNodeRunTime : 0
NormalizedRunTime : 1997602721
IdealCpu : 0
HypercallsCount : 1708169
HypercallsTime : 111914774
PageInvalidationsCount : 0
PageInvalidationsTime : 0
On a root partition with some active child partitions, the entire
directory structure may look like:
mshv/
stats # hypervisor stats
lp/ # logical processors
0/ # LP id
stats # LP 0 stats
1/
2/
3/
partition/ # partition stats
1/ # root partition id
stats # root partition stats
vp/ # root virtual processors
0/ # root VP id
stats # root VP 0 stats
1/
2/
3/
42/ # child partition id
stats # child partition stats
vp/ # child VPs
0/ # child VP id
stats # child VP 0 stats
1/
43/
55/
On L1VH, some stats are not present as it does not own the hardware
like the root partition does:
- The hypervisor and lp stats are not present
- L1VH's partition directory is named "self" because it can't get its
own id
- Some of L1VH's partition and VP stats fields are not populated, because
it can't map its own HV_STATS_AREA_PARENT page.
Co-developed-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Co-developed-by: Praveen K Paladugu <prapal@linux.microsoft.com>
Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
Co-developed-by: Mukesh Rathor <mrathor@linux.microsoft.com>
Signed-off-by: Mukesh Rathor <mrathor@linux.microsoft.com>
Co-developed-by: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
Signed-off-by: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
Co-developed-by: Jinank Jain <jinankjain@microsoft.com>
Signed-off-by: Jinank Jain <jinankjain@microsoft.com>
Signed-off-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
Reviewed-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
---
drivers/hv/Makefile | 1 +
drivers/hv/mshv_debugfs.c | 1122 +++++++++++++++++++++++++++++++++++
drivers/hv/mshv_root.h | 34 ++
drivers/hv/mshv_root_main.c | 32 +-
4 files changed, 1185 insertions(+), 4 deletions(-)
create mode 100644 drivers/hv/mshv_debugfs.c
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 58b8d07639f3..36278c936914 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -15,6 +15,7 @@ hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o
hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o
mshv_root-y := mshv_root_main.o mshv_synic.o mshv_eventfd.o mshv_irq.o \
mshv_root_hv_call.o mshv_portid_table.o
+mshv_root-$(CONFIG_DEBUG_FS) += mshv_debugfs.o
mshv_vtl-y := mshv_vtl_main.o
# Code that must be built-in
diff --git a/drivers/hv/mshv_debugfs.c b/drivers/hv/mshv_debugfs.c
new file mode 100644
index 000000000000..581018690a27
--- /dev/null
+++ b/drivers/hv/mshv_debugfs.c
@@ -0,0 +1,1122 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025, Microsoft Corporation.
+ *
+ * The /sys/kernel/debug/mshv directory contents.
+ * Contains various statistics data, provided by the hypervisor.
+ *
+ * Authors: Microsoft Linux virtualization team
+ */
+
+#include <linux/debugfs.h>
+#include <linux/stringify.h>
+#include <asm/mshyperv.h>
+#include <linux/slab.h>
+
+#include "mshv.h"
+#include "mshv_root.h"
+
+#define U32_BUF_SZ 11
+#define U64_BUF_SZ 21
+
+static struct dentry *mshv_debugfs;
+static struct dentry *mshv_debugfs_partition;
+static struct dentry *mshv_debugfs_lp;
+
+static u64 mshv_lps_count;
+
+static bool is_l1vh_parent(u64 partition_id)
+{
+ return hv_l1vh_partition() && (partition_id == HV_PARTITION_ID_SELF);
+}
+
+static int lp_stats_show(struct seq_file *m, void *v)
+{
+ const struct hv_stats_page *stats = m->private;
+
+#define LP_SEQ_PRINTF(cnt) \
+ seq_printf(m, "%-29s: %llu\n", __stringify(cnt), stats->lp_cntrs[Lp##cnt])
+
+ LP_SEQ_PRINTF(GlobalTime);
+ LP_SEQ_PRINTF(TotalRunTime);
+ LP_SEQ_PRINTF(HypervisorRunTime);
+ LP_SEQ_PRINTF(HardwareInterrupts);
+ LP_SEQ_PRINTF(ContextSwitches);
+ LP_SEQ_PRINTF(InterProcessorInterrupts);
+ LP_SEQ_PRINTF(SchedulerInterrupts);
+ LP_SEQ_PRINTF(TimerInterrupts);
+ LP_SEQ_PRINTF(InterProcessorInterruptsSent);
+ LP_SEQ_PRINTF(ProcessorHalts);
+ LP_SEQ_PRINTF(MonitorTransitionCost);
+ LP_SEQ_PRINTF(ContextSwitchTime);
+ LP_SEQ_PRINTF(C1TransitionsCount);
+ LP_SEQ_PRINTF(C1RunTime);
+ LP_SEQ_PRINTF(C2TransitionsCount);
+ LP_SEQ_PRINTF(C2RunTime);
+ LP_SEQ_PRINTF(C3TransitionsCount);
+ LP_SEQ_PRINTF(C3RunTime);
+ LP_SEQ_PRINTF(RootVpIndex);
+ LP_SEQ_PRINTF(IdleSequenceNumber);
+ LP_SEQ_PRINTF(GlobalTscCount);
+ LP_SEQ_PRINTF(ActiveTscCount);
+ LP_SEQ_PRINTF(IdleAccumulation);
+ LP_SEQ_PRINTF(ReferenceCycleCount0);
+ LP_SEQ_PRINTF(ActualCycleCount0);
+ LP_SEQ_PRINTF(ReferenceCycleCount1);
+ LP_SEQ_PRINTF(ActualCycleCount1);
+ LP_SEQ_PRINTF(ProximityDomainId);
+ LP_SEQ_PRINTF(PostedInterruptNotifications);
+ LP_SEQ_PRINTF(BranchPredictorFlushes);
+#if IS_ENABLED(CONFIG_X86_64)
+ LP_SEQ_PRINTF(L1DataCacheFlushes);
+ LP_SEQ_PRINTF(ImmediateL1DataCacheFlushes);
+ LP_SEQ_PRINTF(MbFlushes);
+ LP_SEQ_PRINTF(CounterRefreshSequenceNumber);
+ LP_SEQ_PRINTF(CounterRefreshReferenceTime);
+ LP_SEQ_PRINTF(IdleAccumulationSnapshot);
+ LP_SEQ_PRINTF(ActiveTscCountSnapshot);
+ LP_SEQ_PRINTF(HwpRequestContextSwitches);
+ LP_SEQ_PRINTF(Placeholder1);
+ LP_SEQ_PRINTF(Placeholder2);
+ LP_SEQ_PRINTF(Placeholder3);
+ LP_SEQ_PRINTF(Placeholder4);
+ LP_SEQ_PRINTF(Placeholder5);
+ LP_SEQ_PRINTF(Placeholder6);
+ LP_SEQ_PRINTF(Placeholder7);
+ LP_SEQ_PRINTF(Placeholder8);
+ LP_SEQ_PRINTF(Placeholder9);
+ LP_SEQ_PRINTF(Placeholder10);
+ LP_SEQ_PRINTF(ReserveGroupId);
+ LP_SEQ_PRINTF(RunningPriority);
+ LP_SEQ_PRINTF(PerfmonInterruptCount);
+#elif IS_ENABLED(CONFIG_ARM64)
+ LP_SEQ_PRINTF(CounterRefreshSequenceNumber);
+ LP_SEQ_PRINTF(CounterRefreshReferenceTime);
+ LP_SEQ_PRINTF(IdleAccumulationSnapshot);
+ LP_SEQ_PRINTF(ActiveTscCountSnapshot);
+ LP_SEQ_PRINTF(HwpRequestContextSwitches);
+ LP_SEQ_PRINTF(Placeholder2);
+ LP_SEQ_PRINTF(Placeholder3);
+ LP_SEQ_PRINTF(Placeholder4);
+ LP_SEQ_PRINTF(Placeholder5);
+ LP_SEQ_PRINTF(Placeholder6);
+ LP_SEQ_PRINTF(Placeholder7);
+ LP_SEQ_PRINTF(Placeholder8);
+ LP_SEQ_PRINTF(Placeholder9);
+ LP_SEQ_PRINTF(SchLocalRunListSize);
+ LP_SEQ_PRINTF(ReserveGroupId);
+ LP_SEQ_PRINTF(RunningPriority);
+#endif
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(lp_stats);
+
+static void mshv_lp_stats_unmap(u32 lp_index, void *stats_page_addr)
+{
+ union hv_stats_object_identity identity = {
+ .lp.lp_index = lp_index,
+ .lp.stats_area_type = HV_STATS_AREA_SELF,
+ };
+ int err;
+
+ err = hv_unmap_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR,
+ stats_page_addr, &identity);
+ if (err)
+ pr_err("%s: failed to unmap logical processor %u stats, err: %d\n",
+ __func__, lp_index, err);
+}
+
+static void __init *mshv_lp_stats_map(u32 lp_index)
+{
+ union hv_stats_object_identity identity = {
+ .lp.lp_index = lp_index,
+ .lp.stats_area_type = HV_STATS_AREA_SELF,
+ };
+ void *stats;
+ int err;
+
+ err = hv_map_stats_page(HV_STATS_OBJECT_LOGICAL_PROCESSOR, &identity,
+ &stats);
+ if (err) {
+ pr_err("%s: failed to map logical processor %u stats, err: %d\n",
+ __func__, lp_index, err);
+ return ERR_PTR(err);
+ }
+
+ return stats;
+}
+
+static void __init *lp_debugfs_stats_create(u32 lp_index, struct dentry *parent)
+{
+ struct dentry *dentry;
+ void *stats;
+
+ stats = mshv_lp_stats_map(lp_index);
+ if (IS_ERR(stats))
+ return stats;
+
+ dentry = debugfs_create_file("stats", 0400, parent,
+ stats, &lp_stats_fops);
+ if (IS_ERR(dentry)) {
+ mshv_lp_stats_unmap(lp_index, stats);
+ return dentry;
+ }
+ return stats;
+}
+
+static int __init lp_debugfs_create(u32 lp_index, struct dentry *parent)
+{
+ struct dentry *idx;
+ char lp_idx_str[U32_BUF_SZ];
+ void *stats;
+ int err;
+
+ sprintf(lp_idx_str, "%u", lp_index);
+
+ idx = debugfs_create_dir(lp_idx_str, parent);
+ if (IS_ERR(idx))
+ return PTR_ERR(idx);
+
+ stats = lp_debugfs_stats_create(lp_index, idx);
+ if (IS_ERR(stats)) {
+ err = PTR_ERR(stats);
+ goto remove_debugfs_lp_idx;
+ }
+
+ return 0;
+
+remove_debugfs_lp_idx:
+ debugfs_remove_recursive(idx);
+ return err;
+}
+
+static void mshv_debugfs_lp_remove(void)
+{
+ int lp_index;
+
+ debugfs_remove_recursive(mshv_debugfs_lp);
+
+ for (lp_index = 0; lp_index < mshv_lps_count; lp_index++)
+ mshv_lp_stats_unmap(lp_index, NULL);
+}
+
+static int __init mshv_debugfs_lp_create(struct dentry *parent)
+{
+ struct dentry *lp_dir;
+ int err, lp_index;
+
+ lp_dir = debugfs_create_dir("lp", parent);
+ if (IS_ERR(lp_dir))
+ return PTR_ERR(lp_dir);
+
+ for (lp_index = 0; lp_index < mshv_lps_count; lp_index++) {
+ err = lp_debugfs_create(lp_index, lp_dir);
+ if (err)
+ goto remove_debugfs_lps;
+ }
+
+ mshv_debugfs_lp = lp_dir;
+
+ return 0;
+
+remove_debugfs_lps:
+ for (lp_index -= 1; lp_index >= 0; lp_index--)
+ mshv_lp_stats_unmap(lp_index, NULL);
+ debugfs_remove_recursive(lp_dir);
+ return err;
+}
+
+static int vp_stats_show(struct seq_file *m, void *v)
+{
+ const struct hv_stats_page **pstats = m->private;
+
+#define VP_SEQ_PRINTF(cnt) \
+do { \
+ if (pstats[HV_STATS_AREA_SELF]->vp_cntrs[Vp##cnt]) \
+ seq_printf(m, "%-30s: %llu\n", __stringify(cnt), \
+ pstats[HV_STATS_AREA_SELF]->vp_cntrs[Vp##cnt]); \
+ else \
+ seq_printf(m, "%-30s: %llu\n", __stringify(cnt), \
+ pstats[HV_STATS_AREA_PARENT]->vp_cntrs[Vp##cnt]); \
+} while (0)
+
+ VP_SEQ_PRINTF(TotalRunTime);
+ VP_SEQ_PRINTF(HypervisorRunTime);
+ VP_SEQ_PRINTF(RemoteNodeRunTime);
+ VP_SEQ_PRINTF(NormalizedRunTime);
+ VP_SEQ_PRINTF(IdealCpu);
+ VP_SEQ_PRINTF(HypercallsCount);
+ VP_SEQ_PRINTF(HypercallsTime);
+#if IS_ENABLED(CONFIG_X86_64)
+ VP_SEQ_PRINTF(PageInvalidationsCount);
+ VP_SEQ_PRINTF(PageInvalidationsTime);
+ VP_SEQ_PRINTF(ControlRegisterAccessesCount);
+ VP_SEQ_PRINTF(ControlRegisterAccessesTime);
+ VP_SEQ_PRINTF(IoInstructionsCount);
+ VP_SEQ_PRINTF(IoInstructionsTime);
+ VP_SEQ_PRINTF(HltInstructionsCount);
+ VP_SEQ_PRINTF(HltInstructionsTime);
+ VP_SEQ_PRINTF(MwaitInstructionsCount);
+ VP_SEQ_PRINTF(MwaitInstructionsTime);
+ VP_SEQ_PRINTF(CpuidInstructionsCount);
+ VP_SEQ_PRINTF(CpuidInstructionsTime);
+ VP_SEQ_PRINTF(MsrAccessesCount);
+ VP_SEQ_PRINTF(MsrAccessesTime);
+ VP_SEQ_PRINTF(OtherInterceptsCount);
+ VP_SEQ_PRINTF(OtherInterceptsTime);
+ VP_SEQ_PRINTF(ExternalInterruptsCount);
+ VP_SEQ_PRINTF(ExternalInterruptsTime);
+ VP_SEQ_PRINTF(PendingInterruptsCount);
+ VP_SEQ_PRINTF(PendingInterruptsTime);
+ VP_SEQ_PRINTF(EmulatedInstructionsCount);
+ VP_SEQ_PRINTF(EmulatedInstructionsTime);
+ VP_SEQ_PRINTF(DebugRegisterAccessesCount);
+ VP_SEQ_PRINTF(DebugRegisterAccessesTime);
+ VP_SEQ_PRINTF(PageFaultInterceptsCount);
+ VP_SEQ_PRINTF(PageFaultInterceptsTime);
+ VP_SEQ_PRINTF(GuestPageTableMaps);
+ VP_SEQ_PRINTF(LargePageTlbFills);
+ VP_SEQ_PRINTF(SmallPageTlbFills);
+ VP_SEQ_PRINTF(ReflectedGuestPageFaults);
+ VP_SEQ_PRINTF(ApicMmioAccesses);
+ VP_SEQ_PRINTF(IoInterceptMessages);
+ VP_SEQ_PRINTF(MemoryInterceptMessages);
+ VP_SEQ_PRINTF(ApicEoiAccesses);
+ VP_SEQ_PRINTF(OtherMessages);
+ VP_SEQ_PRINTF(PageTableAllocations);
+ VP_SEQ_PRINTF(LogicalProcessorMigrations);
+ VP_SEQ_PRINTF(AddressSpaceEvictions);
+ VP_SEQ_PRINTF(AddressSpaceSwitches);
+ VP_SEQ_PRINTF(AddressDomainFlushes);
+ VP_SEQ_PRINTF(AddressSpaceFlushes);
+ VP_SEQ_PRINTF(GlobalGvaRangeFlushes);
+ VP_SEQ_PRINTF(LocalGvaRangeFlushes);
+ VP_SEQ_PRINTF(PageTableEvictions);
+ VP_SEQ_PRINTF(PageTableReclamations);
+ VP_SEQ_PRINTF(PageTableResets);
+ VP_SEQ_PRINTF(PageTableValidations);
+ VP_SEQ_PRINTF(ApicTprAccesses);
+ VP_SEQ_PRINTF(PageTableWriteIntercepts);
+ VP_SEQ_PRINTF(SyntheticInterrupts);
+ VP_SEQ_PRINTF(VirtualInterrupts);
+ VP_SEQ_PRINTF(ApicIpisSent);
+ VP_SEQ_PRINTF(ApicSelfIpisSent);
+ VP_SEQ_PRINTF(GpaSpaceHypercalls);
+ VP_SEQ_PRINTF(LogicalProcessorHypercalls);
+ VP_SEQ_PRINTF(LongSpinWaitHypercalls);
+ VP_SEQ_PRINTF(OtherHypercalls);
+ VP_SEQ_PRINTF(SyntheticInterruptHypercalls);
+ VP_SEQ_PRINTF(VirtualInterruptHypercalls);
+ VP_SEQ_PRINTF(VirtualMmuHypercalls);
+ VP_SEQ_PRINTF(VirtualProcessorHypercalls);
+ VP_SEQ_PRINTF(HardwareInterrupts);
+ VP_SEQ_PRINTF(NestedPageFaultInterceptsCount);
+ VP_SEQ_PRINTF(NestedPageFaultInterceptsTime);
+ VP_SEQ_PRINTF(PageScans);
+ VP_SEQ_PRINTF(LogicalProcessorDispatches);
+ VP_SEQ_PRINTF(WaitingForCpuTime);
+ VP_SEQ_PRINTF(ExtendedHypercalls);
+ VP_SEQ_PRINTF(ExtendedHypercallInterceptMessages);
+ VP_SEQ_PRINTF(MbecNestedPageTableSwitches);
+ VP_SEQ_PRINTF(OtherReflectedGuestExceptions);
+ VP_SEQ_PRINTF(GlobalIoTlbFlushes);
+ VP_SEQ_PRINTF(GlobalIoTlbFlushCost);
+ VP_SEQ_PRINTF(LocalIoTlbFlushes);
+ VP_SEQ_PRINTF(LocalIoTlbFlushCost);
+ VP_SEQ_PRINTF(HypercallsForwardedCount);
+ VP_SEQ_PRINTF(HypercallsForwardingTime);
+ VP_SEQ_PRINTF(PageInvalidationsForwardedCount);
+ VP_SEQ_PRINTF(PageInvalidationsForwardingTime);
+ VP_SEQ_PRINTF(ControlRegisterAccessesForwardedCount);
+ VP_SEQ_PRINTF(ControlRegisterAccessesForwardingTime);
+ VP_SEQ_PRINTF(IoInstructionsForwardedCount);
+ VP_SEQ_PRINTF(IoInstructionsForwardingTime);
+ VP_SEQ_PRINTF(HltInstructionsForwardedCount);
+ VP_SEQ_PRINTF(HltInstructionsForwardingTime);
+ VP_SEQ_PRINTF(MwaitInstructionsForwardedCount);
+ VP_SEQ_PRINTF(MwaitInstructionsForwardingTime);
+ VP_SEQ_PRINTF(CpuidInstructionsForwardedCount);
+ VP_SEQ_PRINTF(CpuidInstructionsForwardingTime);
+ VP_SEQ_PRINTF(MsrAccessesForwardedCount);
+ VP_SEQ_PRINTF(MsrAccessesForwardingTime);
+ VP_SEQ_PRINTF(OtherInterceptsForwardedCount);
+ VP_SEQ_PRINTF(OtherInterceptsForwardingTime);
+ VP_SEQ_PRINTF(ExternalInterruptsForwardedCount);
+ VP_SEQ_PRINTF(ExternalInterruptsForwardingTime);
+ VP_SEQ_PRINTF(PendingInterruptsForwardedCount);
+ VP_SEQ_PRINTF(PendingInterruptsForwardingTime);
+ VP_SEQ_PRINTF(EmulatedInstructionsForwardedCount);
+ VP_SEQ_PRINTF(EmulatedInstructionsForwardingTime);
+ VP_SEQ_PRINTF(DebugRegisterAccessesForwardedCount);
+ VP_SEQ_PRINTF(DebugRegisterAccessesForwardingTime);
+ VP_SEQ_PRINTF(PageFaultInterceptsForwardedCount);
+ VP_SEQ_PRINTF(PageFaultInterceptsForwardingTime);
+ VP_SEQ_PRINTF(VmclearEmulationCount);
+ VP_SEQ_PRINTF(VmclearEmulationTime);
+ VP_SEQ_PRINTF(VmptrldEmulationCount);
+ VP_SEQ_PRINTF(VmptrldEmulationTime);
+ VP_SEQ_PRINTF(VmptrstEmulationCount);
+ VP_SEQ_PRINTF(VmptrstEmulationTime);
+ VP_SEQ_PRINTF(VmreadEmulationCount);
+ VP_SEQ_PRINTF(VmreadEmulationTime);
+ VP_SEQ_PRINTF(VmwriteEmulationCount);
+ VP_SEQ_PRINTF(VmwriteEmulationTime);
+ VP_SEQ_PRINTF(VmxoffEmulationCount);
+ VP_SEQ_PRINTF(VmxoffEmulationTime);
+ VP_SEQ_PRINTF(VmxonEmulationCount);
+ VP_SEQ_PRINTF(VmxonEmulationTime);
+ VP_SEQ_PRINTF(NestedVMEntriesCount);
+ VP_SEQ_PRINTF(NestedVMEntriesTime);
+ VP_SEQ_PRINTF(NestedSLATSoftPageFaultsCount);
+ VP_SEQ_PRINTF(NestedSLATSoftPageFaultsTime);
+ VP_SEQ_PRINTF(NestedSLATHardPageFaultsCount);
+ VP_SEQ_PRINTF(NestedSLATHardPageFaultsTime);
+ VP_SEQ_PRINTF(InvEptAllContextEmulationCount);
+ VP_SEQ_PRINTF(InvEptAllContextEmulationTime);
+ VP_SEQ_PRINTF(InvEptSingleContextEmulationCount);
+ VP_SEQ_PRINTF(InvEptSingleContextEmulationTime);
+ VP_SEQ_PRINTF(InvVpidAllContextEmulationCount);
+ VP_SEQ_PRINTF(InvVpidAllContextEmulationTime);
+ VP_SEQ_PRINTF(InvVpidSingleContextEmulationCount);
+ VP_SEQ_PRINTF(InvVpidSingleContextEmulationTime);
+ VP_SEQ_PRINTF(InvVpidSingleAddressEmulationCount);
+ VP_SEQ_PRINTF(InvVpidSingleAddressEmulationTime);
+ VP_SEQ_PRINTF(NestedTlbPageTableReclamations);
+ VP_SEQ_PRINTF(NestedTlbPageTableEvictions);
+ VP_SEQ_PRINTF(FlushGuestPhysicalAddressSpaceHypercalls);
+ VP_SEQ_PRINTF(FlushGuestPhysicalAddressListHypercalls);
+ VP_SEQ_PRINTF(PostedInterruptNotifications);
+ VP_SEQ_PRINTF(PostedInterruptScans);
+ VP_SEQ_PRINTF(TotalCoreRunTime);
+ VP_SEQ_PRINTF(MaximumRunTime);
+ VP_SEQ_PRINTF(HwpRequestContextSwitches);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket0);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket1);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket2);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket3);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket4);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket5);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket6);
+ VP_SEQ_PRINTF(VmloadEmulationCount);
+ VP_SEQ_PRINTF(VmloadEmulationTime);
+ VP_SEQ_PRINTF(VmsaveEmulationCount);
+ VP_SEQ_PRINTF(VmsaveEmulationTime);
+ VP_SEQ_PRINTF(GifInstructionEmulationCount);
+ VP_SEQ_PRINTF(GifInstructionEmulationTime);
+ VP_SEQ_PRINTF(EmulatedErrataSvmInstructions);
+ VP_SEQ_PRINTF(Placeholder1);
+ VP_SEQ_PRINTF(Placeholder2);
+ VP_SEQ_PRINTF(Placeholder3);
+ VP_SEQ_PRINTF(Placeholder4);
+ VP_SEQ_PRINTF(Placeholder5);
+ VP_SEQ_PRINTF(Placeholder6);
+ VP_SEQ_PRINTF(Placeholder7);
+ VP_SEQ_PRINTF(Placeholder8);
+ VP_SEQ_PRINTF(Placeholder9);
+ VP_SEQ_PRINTF(Placeholder10);
+ VP_SEQ_PRINTF(SchedulingPriority);
+ VP_SEQ_PRINTF(RdpmcInstructionsCount);
+ VP_SEQ_PRINTF(RdpmcInstructionsTime);
+ VP_SEQ_PRINTF(PerfmonPmuMsrAccessesCount);
+ VP_SEQ_PRINTF(PerfmonLbrMsrAccessesCount);
+ VP_SEQ_PRINTF(PerfmonIptMsrAccessesCount);
+ VP_SEQ_PRINTF(PerfmonInterruptCount);
+ VP_SEQ_PRINTF(Vtl1DispatchCount);
+ VP_SEQ_PRINTF(Vtl2DispatchCount);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket0);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket1);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket2);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket3);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket4);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket5);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket6);
+ VP_SEQ_PRINTF(Vtl1RunTime);
+ VP_SEQ_PRINTF(Vtl2RunTime);
+ VP_SEQ_PRINTF(IommuHypercalls);
+ VP_SEQ_PRINTF(CpuGroupHypercalls);
+ VP_SEQ_PRINTF(VsmHypercalls);
+ VP_SEQ_PRINTF(EventLogHypercalls);
+ VP_SEQ_PRINTF(DeviceDomainHypercalls);
+ VP_SEQ_PRINTF(DepositHypercalls);
+ VP_SEQ_PRINTF(SvmHypercalls);
+ VP_SEQ_PRINTF(BusLockAcquisitionCount);
+#elif IS_ENABLED(CONFIG_ARM64)
+ VP_SEQ_PRINTF(SysRegAccessesCount);
+ VP_SEQ_PRINTF(SysRegAccessesTime);
+ VP_SEQ_PRINTF(SmcInstructionsCount);
+ VP_SEQ_PRINTF(SmcInstructionsTime);
+ VP_SEQ_PRINTF(OtherInterceptsCount);
+ VP_SEQ_PRINTF(OtherInterceptsTime);
+ VP_SEQ_PRINTF(ExternalInterruptsCount);
+ VP_SEQ_PRINTF(ExternalInterruptsTime);
+ VP_SEQ_PRINTF(PendingInterruptsCount);
+ VP_SEQ_PRINTF(PendingInterruptsTime);
+ VP_SEQ_PRINTF(GuestPageTableMaps);
+ VP_SEQ_PRINTF(LargePageTlbFills);
+ VP_SEQ_PRINTF(SmallPageTlbFills);
+ VP_SEQ_PRINTF(ReflectedGuestPageFaults);
+ VP_SEQ_PRINTF(MemoryInterceptMessages);
+ VP_SEQ_PRINTF(OtherMessages);
+ VP_SEQ_PRINTF(LogicalProcessorMigrations);
+ VP_SEQ_PRINTF(AddressDomainFlushes);
+ VP_SEQ_PRINTF(AddressSpaceFlushes);
+ VP_SEQ_PRINTF(SyntheticInterrupts);
+ VP_SEQ_PRINTF(VirtualInterrupts);
+ VP_SEQ_PRINTF(ApicSelfIpisSent);
+ VP_SEQ_PRINTF(GpaSpaceHypercalls);
+ VP_SEQ_PRINTF(LogicalProcessorHypercalls);
+ VP_SEQ_PRINTF(LongSpinWaitHypercalls);
+ VP_SEQ_PRINTF(OtherHypercalls);
+ VP_SEQ_PRINTF(SyntheticInterruptHypercalls);
+ VP_SEQ_PRINTF(VirtualInterruptHypercalls);
+ VP_SEQ_PRINTF(VirtualMmuHypercalls);
+ VP_SEQ_PRINTF(VirtualProcessorHypercalls);
+ VP_SEQ_PRINTF(HardwareInterrupts);
+ VP_SEQ_PRINTF(NestedPageFaultInterceptsCount);
+ VP_SEQ_PRINTF(NestedPageFaultInterceptsTime);
+ VP_SEQ_PRINTF(LogicalProcessorDispatches);
+ VP_SEQ_PRINTF(WaitingForCpuTime);
+ VP_SEQ_PRINTF(ExtendedHypercalls);
+ VP_SEQ_PRINTF(ExtendedHypercallInterceptMessages);
+ VP_SEQ_PRINTF(MbecNestedPageTableSwitches);
+ VP_SEQ_PRINTF(OtherReflectedGuestExceptions);
+ VP_SEQ_PRINTF(GlobalIoTlbFlushes);
+ VP_SEQ_PRINTF(GlobalIoTlbFlushCost);
+ VP_SEQ_PRINTF(LocalIoTlbFlushes);
+ VP_SEQ_PRINTF(LocalIoTlbFlushCost);
+ VP_SEQ_PRINTF(FlushGuestPhysicalAddressSpaceHypercalls);
+ VP_SEQ_PRINTF(FlushGuestPhysicalAddressListHypercalls);
+ VP_SEQ_PRINTF(PostedInterruptNotifications);
+ VP_SEQ_PRINTF(PostedInterruptScans);
+ VP_SEQ_PRINTF(TotalCoreRunTime);
+ VP_SEQ_PRINTF(MaximumRunTime);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket0);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket1);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket2);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket3);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket4);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket5);
+ VP_SEQ_PRINTF(WaitingForCpuTimeBucket6);
+ VP_SEQ_PRINTF(HwpRequestContextSwitches);
+ VP_SEQ_PRINTF(Placeholder2);
+ VP_SEQ_PRINTF(Placeholder3);
+ VP_SEQ_PRINTF(Placeholder4);
+ VP_SEQ_PRINTF(Placeholder5);
+ VP_SEQ_PRINTF(Placeholder6);
+ VP_SEQ_PRINTF(Placeholder7);
+ VP_SEQ_PRINTF(Placeholder8);
+ VP_SEQ_PRINTF(ContentionTime);
+ VP_SEQ_PRINTF(WakeUpTime);
+ VP_SEQ_PRINTF(SchedulingPriority);
+ VP_SEQ_PRINTF(Vtl1DispatchCount);
+ VP_SEQ_PRINTF(Vtl2DispatchCount);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket0);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket1);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket2);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket3);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket4);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket5);
+ VP_SEQ_PRINTF(Vtl2DispatchBucket6);
+ VP_SEQ_PRINTF(Vtl1RunTime);
+ VP_SEQ_PRINTF(Vtl2RunTime);
+ VP_SEQ_PRINTF(IommuHypercalls);
+ VP_SEQ_PRINTF(CpuGroupHypercalls);
+ VP_SEQ_PRINTF(VsmHypercalls);
+ VP_SEQ_PRINTF(EventLogHypercalls);
+ VP_SEQ_PRINTF(DeviceDomainHypercalls);
+ VP_SEQ_PRINTF(DepositHypercalls);
+ VP_SEQ_PRINTF(SvmHypercalls);
+#endif
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(vp_stats);
+
+static void mshv_vp_stats_unmap(u64 partition_id, u32 vp_index, void *stats_page_addr,
+ enum hv_stats_area_type stats_area_type)
+{
+ union hv_stats_object_identity identity = {
+ .vp.partition_id = partition_id,
+ .vp.vp_index = vp_index,
+ .vp.stats_area_type = stats_area_type,
+ };
+ int err;
+
+ err = hv_unmap_stats_page(HV_STATS_OBJECT_VP, stats_page_addr, &identity);
+ if (err)
+ pr_err("%s: failed to unmap partition %llu vp %u %s stats, err: %d\n",
+ __func__, partition_id, vp_index,
+ (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent",
+ err);
+}
+
+static void *mshv_vp_stats_map(u64 partition_id, u32 vp_index,
+ enum hv_stats_area_type stats_area_type)
+{
+ union hv_stats_object_identity identity = {
+ .vp.partition_id = partition_id,
+ .vp.vp_index = vp_index,
+ .vp.stats_area_type = stats_area_type,
+ };
+ void *stats;
+ int err;
+
+ err = hv_map_stats_page(HV_STATS_OBJECT_VP, &identity, &stats);
+ if (err) {
+ pr_err("%s: failed to map partition %llu vp %u %s stats, err: %d\n",
+ __func__, partition_id, vp_index,
+ (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent",
+ err);
+ return ERR_PTR(err);
+ }
+ return stats;
+}
+
+static int vp_debugfs_stats_create(u64 partition_id, u32 vp_index,
+ struct dentry **vp_stats_ptr,
+ struct dentry *parent)
+{
+ struct dentry *dentry;
+ struct hv_stats_page **pstats;
+ int err;
+
+ pstats = kcalloc(2, sizeof(struct hv_stats_page *), GFP_KERNEL_ACCOUNT);
+ if (!pstats)
+ return -ENOMEM;
+
+ pstats[HV_STATS_AREA_SELF] = mshv_vp_stats_map(partition_id, vp_index,
+ HV_STATS_AREA_SELF);
+ if (IS_ERR(pstats[HV_STATS_AREA_SELF])) {
+ err = PTR_ERR(pstats[HV_STATS_AREA_SELF]);
+ goto cleanup;
+ }
+
+ /*
+ * L1VH partition cannot access its vp stats in parent area.
+ */
+ if (is_l1vh_parent(partition_id)) {
+ pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF];
+ } else {
+ pstats[HV_STATS_AREA_PARENT] = mshv_vp_stats_map(
+ partition_id, vp_index, HV_STATS_AREA_PARENT);
+ if (IS_ERR(pstats[HV_STATS_AREA_PARENT])) {
+ err = PTR_ERR(pstats[HV_STATS_AREA_PARENT]);
+ goto unmap_self;
+ }
+ if (!pstats[HV_STATS_AREA_PARENT])
+ pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF];
+ }
+
+ dentry = debugfs_create_file("stats", 0400, parent,
+ pstats, &vp_stats_fops);
+ if (IS_ERR(dentry)) {
+ err = PTR_ERR(dentry);
+ goto unmap_vp_stats;
+ }
+
+ *vp_stats_ptr = dentry;
+ return 0;
+
+unmap_vp_stats:
+ if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF])
+ mshv_vp_stats_unmap(partition_id, vp_index, pstats[HV_STATS_AREA_PARENT],
+ HV_STATS_AREA_PARENT);
+unmap_self:
+ mshv_vp_stats_unmap(partition_id, vp_index, pstats[HV_STATS_AREA_SELF],
+ HV_STATS_AREA_SELF);
+cleanup:
+ kfree(pstats);
+ return err;
+}
+
+static void vp_debugfs_remove(u64 partition_id, u32 vp_index,
+ struct dentry *vp_stats)
+{
+ struct hv_stats_page **pstats = NULL;
+ void *stats;
+
+ pstats = vp_stats->d_inode->i_private;
+ debugfs_remove_recursive(vp_stats->d_parent);
+ if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) {
+ stats = pstats[HV_STATS_AREA_PARENT];
+ mshv_vp_stats_unmap(partition_id, vp_index, stats,
+ HV_STATS_AREA_PARENT);
+ }
+
+ stats = pstats[HV_STATS_AREA_SELF];
+ mshv_vp_stats_unmap(partition_id, vp_index, stats, HV_STATS_AREA_SELF);
+
+ kfree(pstats);
+}
+
+static int vp_debugfs_create(u64 partition_id, u32 vp_index,
+ struct dentry **vp_stats_ptr,
+ struct dentry *parent)
+{
+ struct dentry *vp_idx_dir;
+ char vp_idx_str[U32_BUF_SZ];
+ int err;
+
+ sprintf(vp_idx_str, "%u", vp_index);
+
+ vp_idx_dir = debugfs_create_dir(vp_idx_str, parent);
+ if (IS_ERR(vp_idx_dir))
+ return PTR_ERR(vp_idx_dir);
+
+ err = vp_debugfs_stats_create(partition_id, vp_index, vp_stats_ptr,
+ vp_idx_dir);
+ if (err)
+ goto remove_debugfs_vp_idx;
+
+ return 0;
+
+remove_debugfs_vp_idx:
+ debugfs_remove_recursive(vp_idx_dir);
+ return err;
+}
+
+static int partition_stats_show(struct seq_file *m, void *v)
+{
+ const struct hv_stats_page **pstats = m->private;
+
+#define PARTITION_SEQ_PRINTF(cnt) \
+do { \
+ if (pstats[HV_STATS_AREA_SELF]->pt_cntrs[Partition##cnt]) \
+ seq_printf(m, "%-30s: %llu\n", __stringify(cnt), \
+ pstats[HV_STATS_AREA_SELF]->pt_cntrs[Partition##cnt]); \
+ else \
+ seq_printf(m, "%-30s: %llu\n", __stringify(cnt), \
+ pstats[HV_STATS_AREA_PARENT]->pt_cntrs[Partition##cnt]); \
+} while (0)
+
+ PARTITION_SEQ_PRINTF(VirtualProcessors);
+ PARTITION_SEQ_PRINTF(TlbSize);
+ PARTITION_SEQ_PRINTF(AddressSpaces);
+ PARTITION_SEQ_PRINTF(DepositedPages);
+ PARTITION_SEQ_PRINTF(GpaPages);
+ PARTITION_SEQ_PRINTF(GpaSpaceModifications);
+ PARTITION_SEQ_PRINTF(VirtualTlbFlushEntires);
+ PARTITION_SEQ_PRINTF(RecommendedTlbSize);
+ PARTITION_SEQ_PRINTF(GpaPages4K);
+ PARTITION_SEQ_PRINTF(GpaPages2M);
+ PARTITION_SEQ_PRINTF(GpaPages1G);
+ PARTITION_SEQ_PRINTF(GpaPages512G);
+ PARTITION_SEQ_PRINTF(DevicePages4K);
+ PARTITION_SEQ_PRINTF(DevicePages2M);
+ PARTITION_SEQ_PRINTF(DevicePages1G);
+ PARTITION_SEQ_PRINTF(DevicePages512G);
+ PARTITION_SEQ_PRINTF(AttachedDevices);
+ PARTITION_SEQ_PRINTF(DeviceInterruptMappings);
+ PARTITION_SEQ_PRINTF(IoTlbFlushes);
+ PARTITION_SEQ_PRINTF(IoTlbFlushCost);
+ PARTITION_SEQ_PRINTF(DeviceInterruptErrors);
+ PARTITION_SEQ_PRINTF(DeviceDmaErrors);
+ PARTITION_SEQ_PRINTF(DeviceInterruptThrottleEvents);
+ PARTITION_SEQ_PRINTF(SkippedTimerTicks);
+ PARTITION_SEQ_PRINTF(PartitionId);
+#if IS_ENABLED(CONFIG_X86_64)
+ PARTITION_SEQ_PRINTF(NestedTlbSize);
+ PARTITION_SEQ_PRINTF(RecommendedNestedTlbSize);
+ PARTITION_SEQ_PRINTF(NestedTlbFreeListSize);
+ PARTITION_SEQ_PRINTF(NestedTlbTrimmedPages);
+ PARTITION_SEQ_PRINTF(PagesShattered);
+ PARTITION_SEQ_PRINTF(PagesRecombined);
+ PARTITION_SEQ_PRINTF(HwpRequestValue);
+#elif IS_ENABLED(CONFIG_ARM64)
+ PARTITION_SEQ_PRINTF(HwpRequestValue);
+#endif
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(partition_stats);
+
+static void mshv_partition_stats_unmap(u64 partition_id, void *stats_page_addr,
+ enum hv_stats_area_type stats_area_type)
+{
+ union hv_stats_object_identity identity = {
+ .partition.partition_id = partition_id,
+ .partition.stats_area_type = stats_area_type,
+ };
+ int err;
+
+ err = hv_unmap_stats_page(HV_STATS_OBJECT_PARTITION, stats_page_addr,
+ &identity);
+ if (err) {
+ pr_err("%s: failed to unmap partition %lld %s stats, err: %d\n",
+ __func__, partition_id,
+ (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent",
+ err);
+ }
+}
+
+static void *mshv_partition_stats_map(u64 partition_id,
+ enum hv_stats_area_type stats_area_type)
+{
+ union hv_stats_object_identity identity = {
+ .partition.partition_id = partition_id,
+ .partition.stats_area_type = stats_area_type,
+ };
+ void *stats;
+ int err;
+
+ err = hv_map_stats_page(HV_STATS_OBJECT_PARTITION, &identity, &stats);
+ if (err) {
+ pr_err("%s: failed to map partition %lld %s stats, err: %d\n",
+ __func__, partition_id,
+ (stats_area_type == HV_STATS_AREA_SELF) ? "self" : "parent",
+ err);
+ return ERR_PTR(err);
+ }
+ return stats;
+}
+
+static int mshv_debugfs_partition_stats_create(u64 partition_id,
+ struct dentry **partition_stats_ptr,
+ struct dentry *parent)
+{
+ struct dentry *dentry;
+ struct hv_stats_page **pstats;
+ int err;
+
+ pstats = kcalloc(2, sizeof(struct hv_stats_page *), GFP_KERNEL_ACCOUNT);
+ if (!pstats)
+ return -ENOMEM;
+
+ pstats[HV_STATS_AREA_SELF] = mshv_partition_stats_map(partition_id,
+ HV_STATS_AREA_SELF);
+ if (IS_ERR(pstats[HV_STATS_AREA_SELF])) {
+ err = PTR_ERR(pstats[HV_STATS_AREA_SELF]);
+ goto cleanup;
+ }
+
+ /*
+ * L1VH partition cannot access its partition stats in parent area.
+ */
+ if (is_l1vh_parent(partition_id)) {
+ pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF];
+ } else {
+ pstats[HV_STATS_AREA_PARENT] = mshv_partition_stats_map(partition_id,
+ HV_STATS_AREA_PARENT);
+ if (IS_ERR(pstats[HV_STATS_AREA_PARENT])) {
+ err = PTR_ERR(pstats[HV_STATS_AREA_PARENT]);
+ goto unmap_self;
+ }
+ if (!pstats[HV_STATS_AREA_PARENT])
+ pstats[HV_STATS_AREA_PARENT] = pstats[HV_STATS_AREA_SELF];
+ }
+
+ dentry = debugfs_create_file("stats", 0400, parent,
+ pstats, &partition_stats_fops);
+ if (IS_ERR(dentry)) {
+ err = PTR_ERR(dentry);
+ goto unmap_partition_stats;
+ }
+
+ *partition_stats_ptr = dentry;
+ return 0;
+
+unmap_partition_stats:
+ if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF])
+ mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_PARENT],
+ HV_STATS_AREA_PARENT);
+unmap_self:
+ mshv_partition_stats_unmap(partition_id, pstats[HV_STATS_AREA_SELF],
+ HV_STATS_AREA_SELF);
+cleanup:
+ kfree(pstats);
+ return err;
+}
+
+static void partition_debugfs_remove(u64 partition_id, struct dentry *dentry)
+{
+ struct hv_stats_page **pstats = NULL;
+ void *stats;
+
+ pstats = dentry->d_inode->i_private;
+
+ debugfs_remove_recursive(dentry->d_parent);
+
+ if (pstats[HV_STATS_AREA_PARENT] != pstats[HV_STATS_AREA_SELF]) {
+ stats = pstats[HV_STATS_AREA_PARENT];
+ mshv_partition_stats_unmap(partition_id, stats, HV_STATS_AREA_PARENT);
+ }
+
+ stats = pstats[HV_STATS_AREA_SELF];
+ mshv_partition_stats_unmap(partition_id, stats, HV_STATS_AREA_SELF);
+
+ kfree(pstats);
+}
+
+static int partition_debugfs_create(u64 partition_id,
+ struct dentry **vp_dir_ptr,
+ struct dentry **partition_stats_ptr,
+ struct dentry *parent)
+{
+ char part_id_str[U64_BUF_SZ];
+ struct dentry *part_id_dir, *vp_dir;
+ int err;
+
+ if (is_l1vh_parent(partition_id))
+ sprintf(part_id_str, "self");
+ else
+ sprintf(part_id_str, "%llu", partition_id);
+
+ part_id_dir = debugfs_create_dir(part_id_str, parent);
+ if (IS_ERR(part_id_dir))
+ return PTR_ERR(part_id_dir);
+
+ vp_dir = debugfs_create_dir("vp", part_id_dir);
+ if (IS_ERR(vp_dir)) {
+ err = PTR_ERR(vp_dir);
+ goto remove_debugfs_partition_id;
+ }
+
+ err = mshv_debugfs_partition_stats_create(partition_id,
+ partition_stats_ptr,
+ part_id_dir);
+ if (err)
+ goto remove_debugfs_partition_id;
+
+ *vp_dir_ptr = vp_dir;
+
+ return 0;
+
+remove_debugfs_partition_id:
+ debugfs_remove_recursive(part_id_dir);
+ return err;
+}
+
+static void mshv_debugfs_parent_partition_remove(void)
+{
+ int idx;
+
+ for_each_online_cpu(idx)
+ vp_debugfs_remove(hv_current_partition_id, idx, NULL);
+
+ partition_debugfs_remove(hv_current_partition_id, NULL);
+}
+
+static int __init mshv_debugfs_parent_partition_create(void)
+{
+ struct dentry *partition_stats, *vp_dir;
+ int err, idx, i;
+
+ mshv_debugfs_partition = debugfs_create_dir("partition",
+ mshv_debugfs);
+ if (IS_ERR(mshv_debugfs_partition))
+ return PTR_ERR(mshv_debugfs_partition);
+
+ err = partition_debugfs_create(hv_current_partition_id,
+ &vp_dir,
+ &partition_stats,
+ mshv_debugfs_partition);
+ if (err)
+ goto remove_debugfs_partition;
+
+ for_each_online_cpu(idx) {
+ struct dentry *vp_stats;
+
+ err = vp_debugfs_create(hv_current_partition_id,
+ hv_vp_index[idx],
+ &vp_stats,
+ vp_dir);
+ if (err)
+ goto remove_debugfs_partition_vp;
+ }
+
+ return 0;
+
+remove_debugfs_partition_vp:
+ for_each_online_cpu(i) {
+ if (i >= idx)
+ break;
+ vp_debugfs_remove(hv_current_partition_id, i, NULL);
+ }
+ partition_debugfs_remove(hv_current_partition_id, NULL);
+remove_debugfs_partition:
+ debugfs_remove_recursive(mshv_debugfs_partition);
+ return err;
+}
+
+static int hv_stats_show(struct seq_file *m, void *v)
+{
+ const struct hv_stats_page *stats = m->private;
+
+#define HV_SEQ_PRINTF(cnt) \
+ seq_printf(m, "%-25s: %llu\n", __stringify(cnt), stats->hv_cntrs[Hv##cnt])
+
+ HV_SEQ_PRINTF(LogicalProcessors);
+ HV_SEQ_PRINTF(Partitions);
+ HV_SEQ_PRINTF(TotalPages);
+ HV_SEQ_PRINTF(VirtualProcessors);
+ HV_SEQ_PRINTF(MonitoredNotifications);
+ HV_SEQ_PRINTF(ModernStandbyEntries);
+ HV_SEQ_PRINTF(PlatformIdleTransitions);
+ HV_SEQ_PRINTF(HypervisorStartupCost);
+ HV_SEQ_PRINTF(IOSpacePages);
+ HV_SEQ_PRINTF(NonEssentialPagesForDump);
+ HV_SEQ_PRINTF(SubsumedPages);
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(hv_stats);
+
+static void mshv_hv_stats_unmap(void)
+{
+ union hv_stats_object_identity identity = {
+ .hv.stats_area_type = HV_STATS_AREA_SELF,
+ };
+ int err;
+
+ err = hv_unmap_stats_page(HV_STATS_OBJECT_HYPERVISOR, NULL, &identity);
+ if (err)
+ pr_err("%s: failed to unmap hypervisor stats: %d\n",
+ __func__, err);
+}
+
+static void * __init mshv_hv_stats_map(void)
+{
+ union hv_stats_object_identity identity = {
+ .hv.stats_area_type = HV_STATS_AREA_SELF,
+ };
+ void *stats;
+ int err;
+
+ err = hv_map_stats_page(HV_STATS_OBJECT_HYPERVISOR, &identity, &stats);
+ if (err) {
+ pr_err("%s: failed to map hypervisor stats: %d\n",
+ __func__, err);
+ return ERR_PTR(err);
+ }
+ return stats;
+}
+
+static int __init mshv_debugfs_hv_stats_create(struct dentry *parent)
+{
+ struct dentry *dentry;
+ u64 *stats;
+ int err;
+
+ stats = mshv_hv_stats_map();
+ if (IS_ERR(stats))
+ return PTR_ERR(stats);
+
+ dentry = debugfs_create_file("stats", 0400, parent,
+ stats, &hv_stats_fops);
+ if (IS_ERR(dentry)) {
+ err = PTR_ERR(dentry);
+ pr_err("%s: failed to create hypervisor stats dentry: %d\n",
+ __func__, err);
+ goto unmap_hv_stats;
+ }
+
+ mshv_lps_count = stats[HvLogicalProcessors];
+
+ return 0;
+
+unmap_hv_stats:
+ mshv_hv_stats_unmap();
+ return err;
+}
+
+int mshv_debugfs_vp_create(struct mshv_vp *vp)
+{
+ struct mshv_partition *p = vp->vp_partition;
+ int err;
+
+ if (!mshv_debugfs)
+ return 0;
+
+ err = vp_debugfs_create(p->pt_id, vp->vp_index,
+ &vp->vp_debugfs_stats_dentry,
+ p->pt_debugfs_vp_dentry);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+void mshv_debugfs_vp_remove(struct mshv_vp *vp)
+{
+ if (!mshv_debugfs)
+ return;
+
+ vp_debugfs_remove(vp->vp_partition->pt_id, vp->vp_index,
+ vp->vp_debugfs_stats_dentry);
+}
+
+int mshv_debugfs_partition_create(struct mshv_partition *partition)
+{
+ int err;
+
+ if (!mshv_debugfs)
+ return 0;
+
+ err = partition_debugfs_create(partition->pt_id,
+ &partition->pt_debugfs_vp_dentry,
+ &partition->pt_debugfs_stats_dentry,
+ mshv_debugfs_partition);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+void mshv_debugfs_partition_remove(struct mshv_partition *partition)
+{
+ if (!mshv_debugfs)
+ return;
+
+ partition_debugfs_remove(partition->pt_id,
+ partition->pt_debugfs_stats_dentry);
+}
+
+int __init mshv_debugfs_init(void)
+{
+ int err;
+
+ mshv_debugfs = debugfs_create_dir("mshv", NULL);
+ if (IS_ERR(mshv_debugfs)) {
+ pr_err("%s: failed to create debugfs directory\n", __func__);
+ return PTR_ERR(mshv_debugfs);
+ }
+
+ if (hv_root_partition()) {
+ err = mshv_debugfs_hv_stats_create(mshv_debugfs);
+ if (err)
+ goto remove_mshv_dir;
+
+ err = mshv_debugfs_lp_create(mshv_debugfs);
+ if (err)
+ goto unmap_hv_stats;
+ }
+
+ err = mshv_debugfs_parent_partition_create();
+ if (err)
+ goto unmap_lp_stats;
+
+ return 0;
+
+unmap_lp_stats:
+ if (hv_root_partition())
+ mshv_debugfs_lp_remove();
+unmap_hv_stats:
+ if (hv_root_partition())
+ mshv_hv_stats_unmap();
+remove_mshv_dir:
+ debugfs_remove_recursive(mshv_debugfs);
+ return err;
+}
+
+void mshv_debugfs_exit(void)
+{
+ mshv_debugfs_parent_partition_remove();
+
+ if (hv_root_partition()) {
+ mshv_debugfs_lp_remove();
+ mshv_hv_stats_unmap();
+ }
+
+ debugfs_remove_recursive(mshv_debugfs);
+}
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 3eb815011b46..1f1b1984449b 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -51,6 +51,9 @@ struct mshv_vp {
unsigned int kicked_by_hv;
wait_queue_head_t vp_suspend_queue;
} run;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ struct dentry *vp_debugfs_stats_dentry;
+#endif
};
#define vp_fmt(fmt) "p%lluvp%u: " fmt
@@ -128,6 +131,10 @@ struct mshv_partition {
u64 isolation_type;
bool import_completed;
bool pt_initialized;
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ struct dentry *pt_debugfs_stats_dentry;
+ struct dentry *pt_debugfs_vp_dentry;
+#endif
};
#define pt_fmt(fmt) "p%llu: " fmt
@@ -308,6 +315,33 @@ int hv_call_modify_spa_host_access(u64 partition_id, struct page **pages,
int hv_call_get_partition_property_ex(u64 partition_id, u64 property_code, u64 arg,
void *property_value, size_t property_value_sz);
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+int __init mshv_debugfs_init(void);
+void mshv_debugfs_exit(void);
+
+int mshv_debugfs_partition_create(struct mshv_partition *partition);
+void mshv_debugfs_partition_remove(struct mshv_partition *partition);
+int mshv_debugfs_vp_create(struct mshv_vp *vp);
+void mshv_debugfs_vp_remove(struct mshv_vp *vp);
+#else
+static inline int __init mshv_debugfs_init(void)
+{
+ return 0;
+}
+static inline void mshv_debugfs_exit(void) { }
+
+static inline int mshv_debugfs_partition_create(struct mshv_partition *partition)
+{
+ return 0;
+}
+static inline void mshv_debugfs_partition_remove(struct mshv_partition *partition) { }
+static inline int mshv_debugfs_vp_create(struct mshv_vp *vp)
+{
+ return 0;
+}
+static inline void mshv_debugfs_vp_remove(struct mshv_vp *vp) { }
+#endif
+
extern struct mshv_root mshv_root;
extern enum hv_scheduler_type hv_scheduler_type;
extern u8 * __percpu *hv_synic_eventring_tail;
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 19006b788e85..152fcd9b45e6 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -982,6 +982,10 @@ mshv_partition_ioctl_create_vp(struct mshv_partition *partition,
if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
memcpy(vp->vp_stats_pages, stats_pages, sizeof(stats_pages));
+ ret = mshv_debugfs_vp_create(vp);
+ if (ret)
+ goto put_partition;
+
/*
* Keep anon_inode_getfd last: it installs fd in the file struct and
* thus makes the state accessible in user space.
@@ -989,7 +993,7 @@ mshv_partition_ioctl_create_vp(struct mshv_partition *partition,
ret = anon_inode_getfd("mshv_vp", &mshv_vp_fops, vp,
O_RDWR | O_CLOEXEC);
if (ret < 0)
- goto put_partition;
+ goto remove_debugfs_vp;
/* already exclusive with the partition mutex for all ioctls */
partition->pt_vp_count++;
@@ -997,6 +1001,8 @@ mshv_partition_ioctl_create_vp(struct mshv_partition *partition,
return ret;
+remove_debugfs_vp:
+ mshv_debugfs_vp_remove(vp);
put_partition:
mshv_partition_put(partition);
free_vp:
@@ -1556,13 +1562,18 @@ mshv_partition_ioctl_initialize(struct mshv_partition *partition)
ret = hv_call_initialize_partition(partition->pt_id);
if (ret)
- goto withdraw_mem;
+ return ret;
+
+ ret = mshv_debugfs_partition_create(partition);
+ if (ret)
+ goto finalize_partition;
partition->pt_initialized = true;
return 0;
-withdraw_mem:
+finalize_partition:
+ hv_call_finalize_partition(partition->pt_id);
hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, partition->pt_id);
return ret;
@@ -1741,6 +1752,8 @@ static void destroy_partition(struct mshv_partition *partition)
if (!vp)
continue;
+ mshv_debugfs_vp_remove(vp);
+
if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
mshv_vp_stats_unmap(partition->pt_id, vp->vp_index,
(void **)vp->vp_stats_pages);
@@ -1775,6 +1788,8 @@ static void destroy_partition(struct mshv_partition *partition)
partition->pt_vp_array[i] = NULL;
}
+ mshv_debugfs_partition_remove(partition);
+
/* Deallocates and unmaps everything including vcpus, GPA mappings etc */
hv_call_finalize_partition(partition->pt_id);
@@ -2351,10 +2366,14 @@ static int __init mshv_parent_partition_init(void)
mshv_init_vmm_caps(dev);
- ret = mshv_irqfd_wq_init();
+ ret = mshv_debugfs_init();
if (ret)
goto exit_partition;
+ ret = mshv_irqfd_wq_init();
+ if (ret)
+ goto exit_debugfs;
+
spin_lock_init(&mshv_root.pt_ht_lock);
hash_init(mshv_root.pt_htable);
@@ -2362,6 +2381,10 @@ static int __init mshv_parent_partition_init(void)
return 0;
+destroy_irqds_wq:
+ mshv_irqfd_wq_cleanup();
+exit_debugfs:
+ mshv_debugfs_exit();
exit_partition:
if (hv_root_partition())
mshv_root_partition_exit();
@@ -2378,6 +2401,7 @@ static void __exit mshv_parent_partition_exit(void)
{
hv_setup_mshv_handler(NULL);
mshv_port_table_fini();
+ mshv_debugfs_exit();
misc_deregister(&mshv_dev);
mshv_irqfd_wq_cleanup();
if (hv_root_partition())
--
2.34.1
^ permalink raw reply related
* [PATCH v2 2/3] mshv: Add definitions for stats pages
From: Nuno Das Neves @ 2025-12-05 18:58 UTC (permalink / raw)
To: linux-hyperv, linux-kernel, skinsburskii
Cc: kys, haiyangz, wei.liu, decui, longli, mhklinux, prapal, mrathor,
paekkaladevi, Nuno Das Neves
In-Reply-To: <1764961122-31679-1-git-send-email-nunodasneves@linux.microsoft.com>
Add the definitions for hypervisor, logical processor, and partition
stats pages.
Move the definition for the VP stats page to its rightful place in
hvhdk.h, and add the missing members.
These enum members retain their CamelCase style, since they are imported
directly from the hypervisor code They will be stringified when printing
the stats out, and retain more readability in this form.
Signed-off-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
---
drivers/hv/mshv_root_main.c | 17 --
include/hyperv/hvhdk.h | 437 ++++++++++++++++++++++++++++++++++++
2 files changed, 437 insertions(+), 17 deletions(-)
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index f59a4ab47685..19006b788e85 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -38,23 +38,6 @@ MODULE_AUTHOR("Microsoft");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Microsoft Hyper-V root partition VMM interface /dev/mshv");
-/* TODO move this to another file when debugfs code is added */
-enum hv_stats_vp_counters { /* HV_THREAD_COUNTER */
-#if defined(CONFIG_X86)
- VpRootDispatchThreadBlocked = 202,
-#elif defined(CONFIG_ARM64)
- VpRootDispatchThreadBlocked = 94,
-#endif
- VpStatsMaxCounter
-};
-
-struct hv_stats_page {
- union {
- u64 vp_cntrs[VpStatsMaxCounter]; /* VP counters */
- u8 data[HV_HYP_PAGE_SIZE];
- };
-} __packed;
-
struct mshv_root mshv_root;
enum hv_scheduler_type hv_scheduler_type;
diff --git a/include/hyperv/hvhdk.h b/include/hyperv/hvhdk.h
index 469186df7826..51abbcd0ec37 100644
--- a/include/hyperv/hvhdk.h
+++ b/include/hyperv/hvhdk.h
@@ -10,6 +10,443 @@
#include "hvhdk_mini.h"
#include "hvgdk.h"
+enum hv_stats_hypervisor_counters { /* HV_HYPERVISOR_COUNTER */
+ HvLogicalProcessors = 1,
+ HvPartitions = 2,
+ HvTotalPages = 3,
+ HvVirtualProcessors = 4,
+ HvMonitoredNotifications = 5,
+ HvModernStandbyEntries = 6,
+ HvPlatformIdleTransitions = 7,
+ HvHypervisorStartupCost = 8,
+ HvIOSpacePages = 10,
+ HvNonEssentialPagesForDump = 11,
+ HvSubsumedPages = 12,
+ HvStatsMaxCounter
+};
+
+enum hv_stats_partition_counters { /* HV_PROCESS_COUNTER */
+ PartitionVirtualProcessors = 1,
+ PartitionTlbSize = 3,
+ PartitionAddressSpaces = 4,
+ PartitionDepositedPages = 5,
+ PartitionGpaPages = 6,
+ PartitionGpaSpaceModifications = 7,
+ PartitionVirtualTlbFlushEntires = 8,
+ PartitionRecommendedTlbSize = 9,
+ PartitionGpaPages4K = 10,
+ PartitionGpaPages2M = 11,
+ PartitionGpaPages1G = 12,
+ PartitionGpaPages512G = 13,
+ PartitionDevicePages4K = 14,
+ PartitionDevicePages2M = 15,
+ PartitionDevicePages1G = 16,
+ PartitionDevicePages512G = 17,
+ PartitionAttachedDevices = 18,
+ PartitionDeviceInterruptMappings = 19,
+ PartitionIoTlbFlushes = 20,
+ PartitionIoTlbFlushCost = 21,
+ PartitionDeviceInterruptErrors = 22,
+ PartitionDeviceDmaErrors = 23,
+ PartitionDeviceInterruptThrottleEvents = 24,
+ PartitionSkippedTimerTicks = 25,
+ PartitionPartitionId = 26,
+#if IS_ENABLED(CONFIG_X86_64)
+ PartitionNestedTlbSize = 27,
+ PartitionRecommendedNestedTlbSize = 28,
+ PartitionNestedTlbFreeListSize = 29,
+ PartitionNestedTlbTrimmedPages = 30,
+ PartitionPagesShattered = 31,
+ PartitionPagesRecombined = 32,
+ PartitionHwpRequestValue = 33,
+#elif IS_ENABLED(CONFIG_ARM64)
+ PartitionHwpRequestValue = 27,
+#endif
+ PartitionStatsMaxCounter
+};
+
+enum hv_stats_vp_counters { /* HV_THREAD_COUNTER */
+ VpTotalRunTime = 1,
+ VpHypervisorRunTime = 2,
+ VpRemoteNodeRunTime = 3,
+ VpNormalizedRunTime = 4,
+ VpIdealCpu = 5,
+ VpHypercallsCount = 7,
+ VpHypercallsTime = 8,
+#if IS_ENABLED(CONFIG_X86_64)
+ VpPageInvalidationsCount = 9,
+ VpPageInvalidationsTime = 10,
+ VpControlRegisterAccessesCount = 11,
+ VpControlRegisterAccessesTime = 12,
+ VpIoInstructionsCount = 13,
+ VpIoInstructionsTime = 14,
+ VpHltInstructionsCount = 15,
+ VpHltInstructionsTime = 16,
+ VpMwaitInstructionsCount = 17,
+ VpMwaitInstructionsTime = 18,
+ VpCpuidInstructionsCount = 19,
+ VpCpuidInstructionsTime = 20,
+ VpMsrAccessesCount = 21,
+ VpMsrAccessesTime = 22,
+ VpOtherInterceptsCount = 23,
+ VpOtherInterceptsTime = 24,
+ VpExternalInterruptsCount = 25,
+ VpExternalInterruptsTime = 26,
+ VpPendingInterruptsCount = 27,
+ VpPendingInterruptsTime = 28,
+ VpEmulatedInstructionsCount = 29,
+ VpEmulatedInstructionsTime = 30,
+ VpDebugRegisterAccessesCount = 31,
+ VpDebugRegisterAccessesTime = 32,
+ VpPageFaultInterceptsCount = 33,
+ VpPageFaultInterceptsTime = 34,
+ VpGuestPageTableMaps = 35,
+ VpLargePageTlbFills = 36,
+ VpSmallPageTlbFills = 37,
+ VpReflectedGuestPageFaults = 38,
+ VpApicMmioAccesses = 39,
+ VpIoInterceptMessages = 40,
+ VpMemoryInterceptMessages = 41,
+ VpApicEoiAccesses = 42,
+ VpOtherMessages = 43,
+ VpPageTableAllocations = 44,
+ VpLogicalProcessorMigrations = 45,
+ VpAddressSpaceEvictions = 46,
+ VpAddressSpaceSwitches = 47,
+ VpAddressDomainFlushes = 48,
+ VpAddressSpaceFlushes = 49,
+ VpGlobalGvaRangeFlushes = 50,
+ VpLocalGvaRangeFlushes = 51,
+ VpPageTableEvictions = 52,
+ VpPageTableReclamations = 53,
+ VpPageTableResets = 54,
+ VpPageTableValidations = 55,
+ VpApicTprAccesses = 56,
+ VpPageTableWriteIntercepts = 57,
+ VpSyntheticInterrupts = 58,
+ VpVirtualInterrupts = 59,
+ VpApicIpisSent = 60,
+ VpApicSelfIpisSent = 61,
+ VpGpaSpaceHypercalls = 62,
+ VpLogicalProcessorHypercalls = 63,
+ VpLongSpinWaitHypercalls = 64,
+ VpOtherHypercalls = 65,
+ VpSyntheticInterruptHypercalls = 66,
+ VpVirtualInterruptHypercalls = 67,
+ VpVirtualMmuHypercalls = 68,
+ VpVirtualProcessorHypercalls = 69,
+ VpHardwareInterrupts = 70,
+ VpNestedPageFaultInterceptsCount = 71,
+ VpNestedPageFaultInterceptsTime = 72,
+ VpPageScans = 73,
+ VpLogicalProcessorDispatches = 74,
+ VpWaitingForCpuTime = 75,
+ VpExtendedHypercalls = 76,
+ VpExtendedHypercallInterceptMessages = 77,
+ VpMbecNestedPageTableSwitches = 78,
+ VpOtherReflectedGuestExceptions = 79,
+ VpGlobalIoTlbFlushes = 80,
+ VpGlobalIoTlbFlushCost = 81,
+ VpLocalIoTlbFlushes = 82,
+ VpLocalIoTlbFlushCost = 83,
+ VpHypercallsForwardedCount = 84,
+ VpHypercallsForwardingTime = 85,
+ VpPageInvalidationsForwardedCount = 86,
+ VpPageInvalidationsForwardingTime = 87,
+ VpControlRegisterAccessesForwardedCount = 88,
+ VpControlRegisterAccessesForwardingTime = 89,
+ VpIoInstructionsForwardedCount = 90,
+ VpIoInstructionsForwardingTime = 91,
+ VpHltInstructionsForwardedCount = 92,
+ VpHltInstructionsForwardingTime = 93,
+ VpMwaitInstructionsForwardedCount = 94,
+ VpMwaitInstructionsForwardingTime = 95,
+ VpCpuidInstructionsForwardedCount = 96,
+ VpCpuidInstructionsForwardingTime = 97,
+ VpMsrAccessesForwardedCount = 98,
+ VpMsrAccessesForwardingTime = 99,
+ VpOtherInterceptsForwardedCount = 100,
+ VpOtherInterceptsForwardingTime = 101,
+ VpExternalInterruptsForwardedCount = 102,
+ VpExternalInterruptsForwardingTime = 103,
+ VpPendingInterruptsForwardedCount = 104,
+ VpPendingInterruptsForwardingTime = 105,
+ VpEmulatedInstructionsForwardedCount = 106,
+ VpEmulatedInstructionsForwardingTime = 107,
+ VpDebugRegisterAccessesForwardedCount = 108,
+ VpDebugRegisterAccessesForwardingTime = 109,
+ VpPageFaultInterceptsForwardedCount = 110,
+ VpPageFaultInterceptsForwardingTime = 111,
+ VpVmclearEmulationCount = 112,
+ VpVmclearEmulationTime = 113,
+ VpVmptrldEmulationCount = 114,
+ VpVmptrldEmulationTime = 115,
+ VpVmptrstEmulationCount = 116,
+ VpVmptrstEmulationTime = 117,
+ VpVmreadEmulationCount = 118,
+ VpVmreadEmulationTime = 119,
+ VpVmwriteEmulationCount = 120,
+ VpVmwriteEmulationTime = 121,
+ VpVmxoffEmulationCount = 122,
+ VpVmxoffEmulationTime = 123,
+ VpVmxonEmulationCount = 124,
+ VpVmxonEmulationTime = 125,
+ VpNestedVMEntriesCount = 126,
+ VpNestedVMEntriesTime = 127,
+ VpNestedSLATSoftPageFaultsCount = 128,
+ VpNestedSLATSoftPageFaultsTime = 129,
+ VpNestedSLATHardPageFaultsCount = 130,
+ VpNestedSLATHardPageFaultsTime = 131,
+ VpInvEptAllContextEmulationCount = 132,
+ VpInvEptAllContextEmulationTime = 133,
+ VpInvEptSingleContextEmulationCount = 134,
+ VpInvEptSingleContextEmulationTime = 135,
+ VpInvVpidAllContextEmulationCount = 136,
+ VpInvVpidAllContextEmulationTime = 137,
+ VpInvVpidSingleContextEmulationCount = 138,
+ VpInvVpidSingleContextEmulationTime = 139,
+ VpInvVpidSingleAddressEmulationCount = 140,
+ VpInvVpidSingleAddressEmulationTime = 141,
+ VpNestedTlbPageTableReclamations = 142,
+ VpNestedTlbPageTableEvictions = 143,
+ VpFlushGuestPhysicalAddressSpaceHypercalls = 144,
+ VpFlushGuestPhysicalAddressListHypercalls = 145,
+ VpPostedInterruptNotifications = 146,
+ VpPostedInterruptScans = 147,
+ VpTotalCoreRunTime = 148,
+ VpMaximumRunTime = 149,
+ VpHwpRequestContextSwitches = 150,
+ VpWaitingForCpuTimeBucket0 = 151,
+ VpWaitingForCpuTimeBucket1 = 152,
+ VpWaitingForCpuTimeBucket2 = 153,
+ VpWaitingForCpuTimeBucket3 = 154,
+ VpWaitingForCpuTimeBucket4 = 155,
+ VpWaitingForCpuTimeBucket5 = 156,
+ VpWaitingForCpuTimeBucket6 = 157,
+ VpVmloadEmulationCount = 158,
+ VpVmloadEmulationTime = 159,
+ VpVmsaveEmulationCount = 160,
+ VpVmsaveEmulationTime = 161,
+ VpGifInstructionEmulationCount = 162,
+ VpGifInstructionEmulationTime = 163,
+ VpEmulatedErrataSvmInstructions = 164,
+ VpPlaceholder1 = 165,
+ VpPlaceholder2 = 166,
+ VpPlaceholder3 = 167,
+ VpPlaceholder4 = 168,
+ VpPlaceholder5 = 169,
+ VpPlaceholder6 = 170,
+ VpPlaceholder7 = 171,
+ VpPlaceholder8 = 172,
+ VpPlaceholder9 = 173,
+ VpPlaceholder10 = 174,
+ VpSchedulingPriority = 175,
+ VpRdpmcInstructionsCount = 176,
+ VpRdpmcInstructionsTime = 177,
+ VpPerfmonPmuMsrAccessesCount = 178,
+ VpPerfmonLbrMsrAccessesCount = 179,
+ VpPerfmonIptMsrAccessesCount = 180,
+ VpPerfmonInterruptCount = 181,
+ VpVtl1DispatchCount = 182,
+ VpVtl2DispatchCount = 183,
+ VpVtl2DispatchBucket0 = 184,
+ VpVtl2DispatchBucket1 = 185,
+ VpVtl2DispatchBucket2 = 186,
+ VpVtl2DispatchBucket3 = 187,
+ VpVtl2DispatchBucket4 = 188,
+ VpVtl2DispatchBucket5 = 189,
+ VpVtl2DispatchBucket6 = 190,
+ VpVtl1RunTime = 191,
+ VpVtl2RunTime = 192,
+ VpIommuHypercalls = 193,
+ VpCpuGroupHypercalls = 194,
+ VpVsmHypercalls = 195,
+ VpEventLogHypercalls = 196,
+ VpDeviceDomainHypercalls = 197,
+ VpDepositHypercalls = 198,
+ VpSvmHypercalls = 199,
+ VpBusLockAcquisitionCount = 200,
+ VpUnused = 201,
+ VpRootDispatchThreadBlocked = 202,
+#elif IS_ENABLED(CONFIG_ARM64)
+ VpSysRegAccessesCount = 9,
+ VpSysRegAccessesTime = 10,
+ VpSmcInstructionsCount = 11,
+ VpSmcInstructionsTime = 12,
+ VpOtherInterceptsCount = 13,
+ VpOtherInterceptsTime = 14,
+ VpExternalInterruptsCount = 15,
+ VpExternalInterruptsTime = 16,
+ VpPendingInterruptsCount = 17,
+ VpPendingInterruptsTime = 18,
+ VpGuestPageTableMaps = 19,
+ VpLargePageTlbFills = 20,
+ VpSmallPageTlbFills = 21,
+ VpReflectedGuestPageFaults = 22,
+ VpMemoryInterceptMessages = 23,
+ VpOtherMessages = 24,
+ VpLogicalProcessorMigrations = 25,
+ VpAddressDomainFlushes = 26,
+ VpAddressSpaceFlushes = 27,
+ VpSyntheticInterrupts = 28,
+ VpVirtualInterrupts = 29,
+ VpApicSelfIpisSent = 30,
+ VpGpaSpaceHypercalls = 31,
+ VpLogicalProcessorHypercalls = 32,
+ VpLongSpinWaitHypercalls = 33,
+ VpOtherHypercalls = 34,
+ VpSyntheticInterruptHypercalls = 35,
+ VpVirtualInterruptHypercalls = 36,
+ VpVirtualMmuHypercalls = 37,
+ VpVirtualProcessorHypercalls = 38,
+ VpHardwareInterrupts = 39,
+ VpNestedPageFaultInterceptsCount = 40,
+ VpNestedPageFaultInterceptsTime = 41,
+ VpLogicalProcessorDispatches = 42,
+ VpWaitingForCpuTime = 43,
+ VpExtendedHypercalls = 44,
+ VpExtendedHypercallInterceptMessages = 45,
+ VpMbecNestedPageTableSwitches = 46,
+ VpOtherReflectedGuestExceptions = 47,
+ VpGlobalIoTlbFlushes = 48,
+ VpGlobalIoTlbFlushCost = 49,
+ VpLocalIoTlbFlushes = 50,
+ VpLocalIoTlbFlushCost = 51,
+ VpFlushGuestPhysicalAddressSpaceHypercalls = 52,
+ VpFlushGuestPhysicalAddressListHypercalls = 53,
+ VpPostedInterruptNotifications = 54,
+ VpPostedInterruptScans = 55,
+ VpTotalCoreRunTime = 56,
+ VpMaximumRunTime = 57,
+ VpWaitingForCpuTimeBucket0 = 58,
+ VpWaitingForCpuTimeBucket1 = 59,
+ VpWaitingForCpuTimeBucket2 = 60,
+ VpWaitingForCpuTimeBucket3 = 61,
+ VpWaitingForCpuTimeBucket4 = 62,
+ VpWaitingForCpuTimeBucket5 = 63,
+ VpWaitingForCpuTimeBucket6 = 64,
+ VpHwpRequestContextSwitches = 65,
+ VpPlaceholder2 = 66,
+ VpPlaceholder3 = 67,
+ VpPlaceholder4 = 68,
+ VpPlaceholder5 = 69,
+ VpPlaceholder6 = 70,
+ VpPlaceholder7 = 71,
+ VpPlaceholder8 = 72,
+ VpContentionTime = 73,
+ VpWakeUpTime = 74,
+ VpSchedulingPriority = 75,
+ VpVtl1DispatchCount = 76,
+ VpVtl2DispatchCount = 77,
+ VpVtl2DispatchBucket0 = 78,
+ VpVtl2DispatchBucket1 = 79,
+ VpVtl2DispatchBucket2 = 80,
+ VpVtl2DispatchBucket3 = 81,
+ VpVtl2DispatchBucket4 = 82,
+ VpVtl2DispatchBucket5 = 83,
+ VpVtl2DispatchBucket6 = 84,
+ VpVtl1RunTime = 85,
+ VpVtl2RunTime = 86,
+ VpIommuHypercalls = 87,
+ VpCpuGroupHypercalls = 88,
+ VpVsmHypercalls = 89,
+ VpEventLogHypercalls = 90,
+ VpDeviceDomainHypercalls = 91,
+ VpDepositHypercalls = 92,
+ VpSvmHypercalls = 93,
+ VpLoadAvg = 94,
+ VpRootDispatchThreadBlocked = 95,
+#endif
+ VpStatsMaxCounter
+};
+
+enum hv_stats_lp_counters { /* HV_CPU_COUNTER */
+ LpGlobalTime = 1,
+ LpTotalRunTime = 2,
+ LpHypervisorRunTime = 3,
+ LpHardwareInterrupts = 4,
+ LpContextSwitches = 5,
+ LpInterProcessorInterrupts = 6,
+ LpSchedulerInterrupts = 7,
+ LpTimerInterrupts = 8,
+ LpInterProcessorInterruptsSent = 9,
+ LpProcessorHalts = 10,
+ LpMonitorTransitionCost = 11,
+ LpContextSwitchTime = 12,
+ LpC1TransitionsCount = 13,
+ LpC1RunTime = 14,
+ LpC2TransitionsCount = 15,
+ LpC2RunTime = 16,
+ LpC3TransitionsCount = 17,
+ LpC3RunTime = 18,
+ LpRootVpIndex = 19,
+ LpIdleSequenceNumber = 20,
+ LpGlobalTscCount = 21,
+ LpActiveTscCount = 22,
+ LpIdleAccumulation = 23,
+ LpReferenceCycleCount0 = 24,
+ LpActualCycleCount0 = 25,
+ LpReferenceCycleCount1 = 26,
+ LpActualCycleCount1 = 27,
+ LpProximityDomainId = 28,
+ LpPostedInterruptNotifications = 29,
+ LpBranchPredictorFlushes = 30,
+#if IS_ENABLED(CONFIG_X86_64)
+ LpL1DataCacheFlushes = 31,
+ LpImmediateL1DataCacheFlushes = 32,
+ LpMbFlushes = 33,
+ LpCounterRefreshSequenceNumber = 34,
+ LpCounterRefreshReferenceTime = 35,
+ LpIdleAccumulationSnapshot = 36,
+ LpActiveTscCountSnapshot = 37,
+ LpHwpRequestContextSwitches = 38,
+ LpPlaceholder1 = 39,
+ LpPlaceholder2 = 40,
+ LpPlaceholder3 = 41,
+ LpPlaceholder4 = 42,
+ LpPlaceholder5 = 43,
+ LpPlaceholder6 = 44,
+ LpPlaceholder7 = 45,
+ LpPlaceholder8 = 46,
+ LpPlaceholder9 = 47,
+ LpPlaceholder10 = 48,
+ LpReserveGroupId = 49,
+ LpRunningPriority = 50,
+ LpPerfmonInterruptCount = 51,
+#elif IS_ENABLED(CONFIG_ARM64)
+ LpCounterRefreshSequenceNumber = 31,
+ LpCounterRefreshReferenceTime = 32,
+ LpIdleAccumulationSnapshot = 33,
+ LpActiveTscCountSnapshot = 34,
+ LpHwpRequestContextSwitches = 35,
+ LpPlaceholder2 = 36,
+ LpPlaceholder3 = 37,
+ LpPlaceholder4 = 38,
+ LpPlaceholder5 = 39,
+ LpPlaceholder6 = 40,
+ LpPlaceholder7 = 41,
+ LpPlaceholder8 = 42,
+ LpPlaceholder9 = 43,
+ LpSchLocalRunListSize = 44,
+ LpReserveGroupId = 45,
+ LpRunningPriority = 46,
+#endif
+ LpStatsMaxCounter
+};
+
+/*
+ * Hypervisor statsitics page format
+ */
+struct hv_stats_page {
+ union {
+ u64 hv_cntrs[HvStatsMaxCounter]; /* Hypervisor counters */
+ u64 pt_cntrs[PartitionStatsMaxCounter]; /* Partition counters */
+ u64 vp_cntrs[VpStatsMaxCounter]; /* VP counters */
+ u64 lp_cntrs[LpStatsMaxCounter]; /* LP counters */
+ u8 data[HV_HYP_PAGE_SIZE];
+ };
+} __packed;
+
/* Bits for dirty mask of hv_vp_register_page */
#define HV_X64_REGISTER_CLASS_GENERAL 0
#define HV_X64_REGISTER_CLASS_IP 1
--
2.34.1
^ permalink raw reply related
* [PATCH v2 1/3] mshv: Ignore second stats page map result failure
From: Nuno Das Neves @ 2025-12-05 18:58 UTC (permalink / raw)
To: linux-hyperv, linux-kernel, skinsburskii
Cc: kys, haiyangz, wei.liu, decui, longli, mhklinux, prapal, mrathor,
paekkaladevi, Nuno Das Neves
In-Reply-To: <1764961122-31679-1-git-send-email-nunodasneves@linux.microsoft.com>
From: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
Older versions of the hypervisor do not support HV_STATS_AREA_PARENT
and return HV_STATUS_INVALID_PARAMETER for the second stats page
mapping request.
This results a failure in module init. Instead of failing, gracefully
fall back to populating stats_pages[HV_STATS_AREA_PARENT] with the
already-mapped stats_pages[HV_STATS_AREA_SELF].
Signed-off-by: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
Signed-off-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
Reviewed-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
---
drivers/hv/mshv_root_hv_call.c | 41 ++++++++++++++++++++++++++++++----
drivers/hv/mshv_root_main.c | 3 +++
2 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
index 598eaff4ff29..b1770c7b500c 100644
--- a/drivers/hv/mshv_root_hv_call.c
+++ b/drivers/hv/mshv_root_hv_call.c
@@ -855,6 +855,24 @@ static int hv_call_map_stats_page2(enum hv_stats_object_type type,
return ret;
}
+static int
+hv_stats_get_area_type(enum hv_stats_object_type type,
+ const union hv_stats_object_identity *identity)
+{
+ switch (type) {
+ case HV_STATS_OBJECT_HYPERVISOR:
+ return identity->hv.stats_area_type;
+ case HV_STATS_OBJECT_LOGICAL_PROCESSOR:
+ return identity->lp.stats_area_type;
+ case HV_STATS_OBJECT_PARTITION:
+ return identity->partition.stats_area_type;
+ case HV_STATS_OBJECT_VP:
+ return identity->vp.stats_area_type;
+ }
+
+ return -EINVAL;
+}
+
static int hv_call_map_stats_page(enum hv_stats_object_type type,
const union hv_stats_object_identity *identity,
void **addr)
@@ -863,7 +881,7 @@ static int hv_call_map_stats_page(enum hv_stats_object_type type,
struct hv_input_map_stats_page *input;
struct hv_output_map_stats_page *output;
u64 status, pfn;
- int ret = 0;
+ int hv_status, ret = 0;
do {
local_irq_save(flags);
@@ -878,11 +896,26 @@ static int hv_call_map_stats_page(enum hv_stats_object_type type,
pfn = output->map_location;
local_irq_restore(flags);
- if (hv_result(status) != HV_STATUS_INSUFFICIENT_MEMORY) {
- ret = hv_result_to_errno(status);
+
+ hv_status = hv_result(status);
+ if (hv_status != HV_STATUS_INSUFFICIENT_MEMORY) {
if (hv_result_success(status))
break;
- return ret;
+
+ /*
+ * Older versions of the hypervisor do not support the
+ * PARENT stats area. In this case return "success" but
+ * set the page to NULL. The caller should check for
+ * this case and instead just use the SELF area.
+ */
+ if (hv_stats_get_area_type(type, identity) == HV_STATS_AREA_PARENT &&
+ hv_status == HV_STATUS_INVALID_PARAMETER) {
+ *addr = NULL;
+ return 0;
+ }
+
+ hv_status_debug(status, "\n");
+ return hv_result_to_errno(status);
}
ret = hv_call_deposit_pages(NUMA_NO_NODE,
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index bc15d6f6922f..f59a4ab47685 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -905,6 +905,9 @@ static int mshv_vp_stats_map(u64 partition_id, u32 vp_index,
if (err)
goto unmap_self;
+ if (!stats_pages[HV_STATS_AREA_PARENT])
+ stats_pages[HV_STATS_AREA_PARENT] = stats_pages[HV_STATS_AREA_SELF];
+
return 0;
unmap_self:
--
2.34.1
^ permalink raw reply related
* [PATCH v2 0/3] mshv: Debugfs interface for mshv_root
From: Nuno Das Neves @ 2025-12-05 18:58 UTC (permalink / raw)
To: linux-hyperv, linux-kernel, skinsburskii
Cc: kys, haiyangz, wei.liu, decui, longli, mhklinux, prapal, mrathor,
paekkaladevi, Nuno Das Neves
Expose hypervisor, logical processor, partition, and virtual processor
statistics via debugfs. These are provided by mapping 'stats' pages via
hypercall.
Patch #1: Update hv_call_map_stats_page() to return success when
HV_STATS_AREA_PARENT is unavailable, which is the case on some
hypervisor versions, where it can fall back to HV_STATS_AREA_SELF
Patch #2: Introduce the definitions needed for the various stats pages
Patch #3: Add mshv_debugfs.c, and integrate it with the mshv_root driver to
expose the partition and VP stats.
---
Changes in v2:
- Remove unnecessary pr_debug_once() in patch 1 [Stanislav Kinsburskii]
- CONFIG_X86 -> CONFIG_X86_64 in patch 2 [Stanislav Kinsburskii]
---
Nuno Das Neves (2):
mshv: Add definitions for stats pages
mshv: Add debugfs to view hypervisor statistics
Purna Pavan Chandra Aekkaladevi (1):
mshv: Ignore second stats page map result failure
drivers/hv/Makefile | 1 +
drivers/hv/mshv_debugfs.c | 1122 ++++++++++++++++++++++++++++++++
drivers/hv/mshv_root.h | 34 +
drivers/hv/mshv_root_hv_call.c | 41 +-
drivers/hv/mshv_root_main.c | 52 +-
include/hyperv/hvhdk.h | 437 +++++++++++++
6 files changed, 1662 insertions(+), 25 deletions(-)
create mode 100644 drivers/hv/mshv_debugfs.c
--
2.34.1
^ permalink raw reply
* Re: [PATCH 0/9] KVM: SVM: Fix (hilarious) exit_code bugs
From: Sean Christopherson @ 2025-12-05 16:59 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, Vitaly Kuznetsov,
K. Y. Srinivasan, Haiyang Zhang, Wei Liu, Dexuan Cui
Cc: kvm, linux-hyperv, linux-kernel, Jim Mattson, Yosry Ahmed
In-Reply-To: <20251113225621.1688428-1-seanjc@google.com>
On Thu, 13 Nov 2025 14:56:12 -0800, Sean Christopherson wrote:
> Hyper-V folks, y'all are getting Cc'd because of a change in
> include/hyperv/hvgdk.h to ensure HV_SVM_EXITCODE_ENL is an unsigned value.
> AFAICT, only KVM consumes that macro. That said, any insight you can provide
> on relevant Hyper-V behavior would be appreciated :-)
>
>
> Fix bugs in SVM that mostly impact nested SVM where KVM treats exit codes
> as 32-bit values instead of 64-bit values. I have no idea how KVM ended up
> with such an egregious flaw, as the blame trail goes all the way back to
> commit 6aa8b732ca01 ("[PATCH] kvm: userspace interface"). Maybe there was
> pre-production hardware or something?
>
> [...]
Applied 1 and 2 to kvm-x86 fixes. I'll send v2 for the rest soon-ish.
[1/9] KVM: nSVM: Clear exit_code_hi in VMCB when synthesizing nested VM-Exits
https://github.com/kvm-x86/linux/commit/da01f64e7470
[2/9] KVM: nSVM: Set exit_code_hi to -1 when synthesizing SVM_EXIT_ERR (failed VMRUN)
https://github.com/kvm-x86/linux/commit/f402ecd7a8b6
--
https://github.com/kvm-x86/linux/tree/next
^ permalink raw reply
* Re: [RFC PATCH] Drivers: hv: Confidential VMBus exernal memory support
From: Tianyu Lan @ 2025-12-05 4:10 UTC (permalink / raw)
To: Michael Kelley
Cc: Christoph Hellwig, Robin Murphy, kys@microsoft.com,
haiyangz@microsoft.com, wei.liu@kernel.org, decui@microsoft.com,
longli@microsoft.com, vdso@hexbites.dev, Tianyu Lan,
linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <CAMvTesB7shuS8HYifEN37bHPR0mWx9c4ZWNH8_cJwXOywa93zQ@mail.gmail.com>
On Thu, Dec 4, 2025 at 7:35 PM Tianyu Lan <ltykernel@gmail.com> wrote:
>
> On Thu, Dec 4, 2025 at 11:35 AM Michael Kelley <mhklinux@outlook.com> wrote:
> >
> > From: Tianyu Lan <ltykernel@gmail.com> Sent: Wednesday, December 3, 2025 6:21 AM
> > >
> > > On Sat, Nov 29, 2025 at 1:47 AM Michael Kelley <mhklinux@outlook.com> wrote:
> > > >
> > > > From: Tianyu Lan <ltykernel@gmail.com> Sent: Monday, November 24, 2025 10:29 AM
> >
> > [snip]
> >
> > > >
> > > > Here's my idea for an alternate approach. The goal is to allow use of the
> > > > swiotlb to be disabled on a per-device basis. A device is initialized for swiotlb
> > > > usage by swiotlb_dev_init(), which sets dev->dma_io_tlb_mem to point to the
> > > > default swiotlb memory. For VMBus devices, the calling sequence is
> > > > vmbus_device_register() -> device_register() -> device_initialize() ->
> > > > swiotlb_dev_init(). But if vmbus_device_register() could override the
> > > > dev->dma_io_tlb_mem value and put it back to NULL, swiotlb operations
> > > > would be disabled on the device. Furthermore, is_swiotlb_force_bounce()
> > > > would return "false", and the normal DMA functions would not force the
> > > > use of bounce buffers. The entire code change looks like this:
> > > >
> > > > --- a/drivers/hv/vmbus_drv.c
> > > > +++ b/drivers/hv/vmbus_drv.c
> > > > @@ -2133,11 +2133,15 @@ int vmbus_device_register(struct hv_device *child_device_obj)
> > > > child_device_obj->device.dma_mask = &child_device_obj->dma_mask;
> > > > dma_set_mask(&child_device_obj->device, DMA_BIT_MASK(64));
> > > >
> > > > + device_initialize(&child_device_obj->device);
> > > > + if (child_device_obj->channel->co_external_memory)
> > > > + child_device_obj->device.dma_io_tlb_mem = NULL;
> > > > +
> > > > /*
> > > > * Register with the LDM. This will kick off the driver/device
> > > > * binding...which will eventually call vmbus_match() and vmbus_probe()
> > > > */
> > > > - ret = device_register(&child_device_obj->device);
> > > > + ret = device_add(&child_device_obj->device);
> > > > if (ret) {
> > > > pr_err("Unable to register child device\n");
> > > > put_device(&child_device_obj->device);
> > > >
> > > > I've only compile tested the above since I don't have an environment where
> > > > I can test Confidential VMBus. You would need to verify whether my thinking
> > > > is correct and this produces the intended result.
> > >
> > > Thanks Michael. I tested it and it seems to hit an issue. Will double check.with
> > > HCL/paravisor team.
> > >
> > > We considered such a change before. From Roman's previous patch, it seems to
> > > need to change phys_to_dma() and force_dma_unencrypted().
> >
> > In a Hyper-V SEV-SNP VM with a paravisor, I assert that phys_to_dma() and
> > __phys_to_dma() do the same thing. phys_to_dma() calls dma_addr_encrypted(),
> > which does __sme_set(). But in a Hyper-V VM using vTOM, sme_me_mask is
> > always 0, so dma_addr_encrypted() is a no-op. dma_addr_unencrypted() and
> > dma_addr_canonical() are also no-ops. See include/linux/mem_encrypt.h. So
> > in a Hyper-V SEV-SNP VM, the DMA layer doesn't change anything related to
> > encryption when translating between a physical address and a DMA address.
> > Same thing is true for a Hyper-V TDX VM with paravisor.
> >
> > force_dma_unencrypted() will indeed return "true", and it is used in
> > phys_to_dma_direct(). But both return paths in phys_to_dma_direct() return the
> > same result because of dma_addr_unencrypted() and dma_addr_encrypted()
> > being no-ops. Other uses of force_dma_unencrypted() are only in the
> > dma_alloc_*() paths, but dma_alloc_*() isn't used by VMBus devices because
> > the device control structures are in the ring buffer, which as you have noted, is
> > already handled separately. So for the moment, I don't think the return value
> > from force_dma_unencrypted() matters.
> >
dma_alloc_*() is used by PCI device driver(e.g, Mana NIC
driver) and If we need to support TDisp device, the change
in the force_dma_unencrypted() is still necessary and dma
address to TDisp device should be encrypted memory with
sme_me_mask.
From this point, Hyper-V specific dma ops may resolve this
without change in the DMA core code. Otherwise, we still
need to add a callback or other flag inside for platforms to
check whether it should return encrypted/decrypted address
to drivers.
> > So I'm guessing something else unexpected is happening such that just disabling
> > the swiotlb on a per-device basis doesn't work. Assuming that Roman's original
> > patch actually worked, I'm trying to figure out how my idea is different in a way
> > that has a material effect on things. And if your patch works by going directly to
> > __phys_to_dma(), it should also work when using phys_to_dma() instead.
> >
The issue I met should not be related with bounce buffer disabling.
I don't find any failure to map dma memory with bounce buffer.
For disabling per-device swiotlb, it looks like work to set dma_io_
tlb_mem to be NULL and it makes is_swiotlb_force_bounce()
returns false.
--
Thanks
Tianyu Lan
^ permalink raw reply
* Re: [PATCH v7 6/7] Drivers: hv: Add refcount and locking to mem regions
From: Stanislav Kinsburskii @ 2025-12-04 21:23 UTC (permalink / raw)
To: Michael Kelley
Cc: kys@microsoft.com, haiyangz@microsoft.com, wei.liu@kernel.org,
decui@microsoft.com, linux-hyperv@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <SN6PR02MB4157553798D947A4B205AEF7D4A6A@SN6PR02MB4157.namprd02.prod.outlook.com>
On Thu, Dec 04, 2025 at 04:48:01PM +0000, Michael Kelley wrote:
> From: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com> Sent: Tuesday, November 25, 2025 6:09 PM
> >
> > Introduce kref-based reference counting and spinlock protection for
> > memory regions in Hyper-V partition management. This change improves
> > memory region lifecycle management and ensures thread-safe access to the
> > region list.
> >
> > Also improves the check for overlapped memory regions during region
> > creation, preventing duplicate or conflicting mappings.
>
> This paragraph seems spurious. I think it applies to what's in Patch 5 of this
> series.
>
Indeed,this chunk escaped cleanup after refactoring.
> >
> > Previously, the regions list was protected by the partition mutex.
> > However, this approach is too heavy for frequent fault and invalidation
> > operations. Finer grained locking is now used to improve efficiency and
> > concurrency.
> >
> > This is a precursor to supporting movable memory regions. Fault and
> > invalidation handling for movable regions will require safe traversal of
> > the region list and holding a region reference while performing
> > invalidation or fault operations.
>
> The commit message discussion about the need for the refcounting and
> locking seemed a bit vague to me. It wasn't entirely clear whether these
> changes are bug fixing existing race conditions, or whether they are new
> functionality to support movable regions.
>
> In looking at the existing code, it seems that the main serialization mechanisms
> are that partition ioctls are serialized on pt_mutex, and VP ioctls are serialized
> on vp_mutex (though multiple VP ioctls can be in progress simultaneously
> against different VPs). The serialization of partition ioctls ensures that region
> manipulation is serialized, and that, for example, two region creations can't
> both verify that there's no overlap, but then overlap with each other. And
> region creation and deletion are serialized. In current code, the VP ioctls don't
> look at the region data structures, so there can't be any races between
> partition and VP ioctls (which are not serialized with each other). The only
> question I had about existing code is the mshv_partition_release() function,
> which proceeds without serializing against any partition ioctls, but maybe
> higher-level file system code ensures that no ioctls are in progress before
> the .release callback is made.
>
> The new requirement is movable regions, where the VP ioctl MSHV_RUN_VP
> needs to look at region data structures. You've said that in the last paragraph
> of your commit message. So I'm reading this as that the new locking is
> needed specifically because multiple MSHV_RUN_VP ioctls will likely be
> in flight simultaneously, and they are not currently serialized with the
> region operations initiated by partition ioctls. And then there are the
> "invalidate" callbacks that are running on some other kernel thread and
> which also needs synchronization to do region manipulation.
>
> Maybe I'm just looking for a little bit of a written "road map" somewhere
> that describes the intended locking scheme at a high level. :-)
>
> Michael
>
You understand this correctly.
In short, there were only two concurrent operations on regions before
movable pages were introduced: addition and removal. Both could happen
only via the partition ioctl, which is serialized by the partition
mutex, so everything was simple.
With the introduction of movable pages, regions — both the list of
regions and the region contents themselves — are accessed by partition
VP threads, which do not hold the partition mutex. While access to
region contents is protected by a per-region mutex, nothing prevents the
VMM from removing and destroying a region from underneath a VP thread
that is currently servicing a page fault or invalidation. This, in turn,
leads to a general protection fault.
This commit solves the issue by making the region a reference-counted
object so it persists while being serviced, and by adding a spinlock to
protect list traversal.
Thanks, Stanislav
> >
> > Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
> > ---
> > drivers/hv/mshv_regions.c | 19 ++++++++++++++++---
> > drivers/hv/mshv_root.h | 6 +++++-
> > drivers/hv/mshv_root_main.c | 34 ++++++++++++++++++++++++++--------
> > 3 files changed, 47 insertions(+), 12 deletions(-)
> >
> > diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
> > index d535d2e3e811..6450a7ed8493 100644
> > --- a/drivers/hv/mshv_regions.c
> > +++ b/drivers/hv/mshv_regions.c
> > @@ -7,6 +7,7 @@
> > * Authors: Microsoft Linux virtualization team
> > */
> >
> > +#include <linux/kref.h>
> > #include <linux/mm.h>
> > #include <linux/vmalloc.h>
> >
> > @@ -154,6 +155,8 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
> > if (!is_mmio)
> > region->flags.range_pinned = true;
> >
> > + kref_init(®ion->refcount);
> > +
> > return region;
> > }
> >
> > @@ -303,13 +306,13 @@ static int mshv_region_unmap(struct mshv_mem_region *region)
> > mshv_region_chunk_unmap);
> > }
> >
> > -void mshv_region_destroy(struct mshv_mem_region *region)
> > +static void mshv_region_destroy(struct kref *ref)
> > {
> > + struct mshv_mem_region *region =
> > + container_of(ref, struct mshv_mem_region, refcount);
> > struct mshv_partition *partition = region->partition;
> > int ret;
> >
> > - hlist_del(®ion->hnode);
> > -
> > if (mshv_partition_encrypted(partition)) {
> > ret = mshv_region_share(region);
> > if (ret) {
> > @@ -326,3 +329,13 @@ void mshv_region_destroy(struct mshv_mem_region *region)
> >
> > vfree(region);
> > }
> > +
> > +void mshv_region_put(struct mshv_mem_region *region)
> > +{
> > + kref_put(®ion->refcount, mshv_region_destroy);
> > +}
> > +
> > +int mshv_region_get(struct mshv_mem_region *region)
> > +{
> > + return kref_get_unless_zero(®ion->refcount);
> > +}
> > diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
> > index ff3374f13691..4249534ba900 100644
> > --- a/drivers/hv/mshv_root.h
> > +++ b/drivers/hv/mshv_root.h
> > @@ -72,6 +72,7 @@ do { \
> >
> > struct mshv_mem_region {
> > struct hlist_node hnode;
> > + struct kref refcount;
> > u64 nr_pages;
> > u64 start_gfn;
> > u64 start_uaddr;
> > @@ -97,6 +98,8 @@ struct mshv_partition {
> > u64 pt_id;
> > refcount_t pt_ref_count;
> > struct mutex pt_mutex;
> > +
> > + spinlock_t pt_mem_regions_lock;
> > struct hlist_head pt_mem_regions; // not ordered
> >
> > u32 pt_vp_count;
> > @@ -319,6 +322,7 @@ int mshv_region_unshare(struct mshv_mem_region *region);
> > int mshv_region_map(struct mshv_mem_region *region);
> > void mshv_region_invalidate(struct mshv_mem_region *region);
> > int mshv_region_pin(struct mshv_mem_region *region);
> > -void mshv_region_destroy(struct mshv_mem_region *region);
> > +void mshv_region_put(struct mshv_mem_region *region);
> > +int mshv_region_get(struct mshv_mem_region *region);
> >
> > #endif /* _MSHV_ROOT_H_ */
> > diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> > index ae600b927f49..1ef2a28beb17 100644
> > --- a/drivers/hv/mshv_root_main.c
> > +++ b/drivers/hv/mshv_root_main.c
> > @@ -1086,9 +1086,13 @@ static int mshv_partition_create_region(struct mshv_partition *partition,
> > u64 nr_pages = HVPFN_DOWN(mem->size);
> >
> > /* Reject overlapping regions */
> > + spin_lock(&partition->pt_mem_regions_lock);
> > if (mshv_partition_region_by_gfn(partition, mem->guest_pfn) ||
> > - mshv_partition_region_by_gfn(partition, mem->guest_pfn + nr_pages - 1))
> > + mshv_partition_region_by_gfn(partition, mem->guest_pfn + nr_pages - 1)) {
> > + spin_unlock(&partition->pt_mem_regions_lock);
> > return -EEXIST;
> > + }
> > + spin_unlock(&partition->pt_mem_regions_lock);
> >
> > rg = mshv_region_create(mem->guest_pfn, nr_pages,
> > mem->userspace_addr, mem->flags,
> > @@ -1220,8 +1224,9 @@ mshv_map_user_memory(struct mshv_partition *partition,
> > if (ret)
> > goto errout;
> >
> > - /* Install the new region */
> > + spin_lock(&partition->pt_mem_regions_lock);
> > hlist_add_head(®ion->hnode, &partition->pt_mem_regions);
> > + spin_unlock(&partition->pt_mem_regions_lock);
> >
> > return 0;
> >
> > @@ -1240,17 +1245,27 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
> > if (!(mem.flags & BIT(MSHV_SET_MEM_BIT_UNMAP)))
> > return -EINVAL;
> >
> > + spin_lock(&partition->pt_mem_regions_lock);
> > +
> > region = mshv_partition_region_by_gfn(partition, mem.guest_pfn);
> > - if (!region)
> > - return -EINVAL;
> > + if (!region) {
> > + spin_unlock(&partition->pt_mem_regions_lock);
> > + return -ENOENT;
> > + }
> >
> > /* Paranoia check */
> > if (region->start_uaddr != mem.userspace_addr ||
> > region->start_gfn != mem.guest_pfn ||
> > - region->nr_pages != HVPFN_DOWN(mem.size))
> > + region->nr_pages != HVPFN_DOWN(mem.size)) {
> > + spin_unlock(&partition->pt_mem_regions_lock);
> > return -EINVAL;
> > + }
> > +
> > + hlist_del(®ion->hnode);
> >
> > - mshv_region_destroy(region);
> > + spin_unlock(&partition->pt_mem_regions_lock);
> > +
> > + mshv_region_put(region);
> >
> > return 0;
> > }
> > @@ -1653,8 +1668,10 @@ static void destroy_partition(struct mshv_partition *partition)
> > remove_partition(partition);
> >
> > hlist_for_each_entry_safe(region, n, &partition->pt_mem_regions,
> > - hnode)
> > - mshv_region_destroy(region);
> > + hnode) {
> > + hlist_del(®ion->hnode);
> > + mshv_region_put(region);
> > + }
> >
> > /* Withdraw and free all pages we deposited */
> > hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, partition->pt_id);
> > @@ -1852,6 +1869,7 @@ mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev)
> >
> > INIT_HLIST_HEAD(&partition->pt_devices);
> >
> > + spin_lock_init(&partition->pt_mem_regions_lock);
> > INIT_HLIST_HEAD(&partition->pt_mem_regions);
> >
> > mshv_eventfd_init(partition);
> >
> >
>
^ permalink raw reply
* Re: [PATCH v7 4/7] Drivers: hv: Fix huge page handling in memory region traversal
From: Stanislav Kinsburskii @ 2025-12-04 21:08 UTC (permalink / raw)
To: Michael Kelley
Cc: kys@microsoft.com, haiyangz@microsoft.com, wei.liu@kernel.org,
decui@microsoft.com, linux-hyperv@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <SN6PR02MB4157F10A84C4BE170AB040F4D4A6A@SN6PR02MB4157.namprd02.prod.outlook.com>
On Thu, Dec 04, 2025 at 04:03:26PM +0000, Michael Kelley wrote:
> From: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com> Sent: Tuesday, November 25, 2025 6:09 PM
> >
> > The previous code assumed that if a region's first page was huge, the
> > entire region consisted of huge pages and stored this in a large_pages
> > flag. This premise is incorrect not only for movable regions (where
> > pages can be split and merged on invalidate callbacks or page faults),
> > but even for pinned regions: THPs can be split and merged during
> > allocation, so a large, pinned region may contain a mix of huge and
> > regular pages.
> >
> > This change removes the large_pages flag and replaces region-wide
> > assumptions with per-chunk inspection of the actual page size when
> > mapping, unmapping, sharing, and unsharing. This makes huge page
> > handling correct for mixed-page regions and avoids relying on stale
> > metadata that can easily become invalid as memory is remapped.
> >
> > Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
> > ---
> > drivers/hv/mshv_regions.c | 213 +++++++++++++++++++++++++++++++++++++++-
> > -----
> > drivers/hv/mshv_root.h | 3 -
> > 2 files changed, 184 insertions(+), 32 deletions(-)
> >
> > diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
> > index 35b866670840..d535d2e3e811 100644
> > --- a/drivers/hv/mshv_regions.c
> > +++ b/drivers/hv/mshv_regions.c
> > @@ -14,6 +14,124 @@
> >
> > #include "mshv_root.h"
> >
> > +/**
> > + * mshv_region_process_chunk - Processes a contiguous chunk of memory pages
> > + * in a region.
> > + * @region : Pointer to the memory region structure.
> > + * @flags : Flags to pass to the handler.
> > + * @page_offset: Offset into the region's pages array to start processing.
> > + * @page_count : Number of pages to process.
> > + * @handler : Callback function to handle the chunk.
> > + *
> > + * This function scans the region's pages starting from @page_offset,
> > + * checking for contiguous present pages of the same size (normal or huge).
> > + * It invokes @handler for the chunk of contiguous pages found. Returns the
> > + * number of pages handled, or a negative error code if the first page is
> > + * not present or the handler fails.
> > + *
> > + * Note: The @handler callback must be able to handle both normal and huge
> > + * pages.
> > + *
> > + * Return: Number of pages handled, or negative error code.
> > + */
> > +static long mshv_region_process_chunk(struct mshv_mem_region *region,
> > + u32 flags,
> > + u64 page_offset, u64 page_count,
> > + int (*handler)(struct mshv_mem_region *region,
> > + u32 flags,
> > + u64 page_offset,
> > + u64 page_count))
> > +{
> > + u64 count, stride;
> > + unsigned int page_order;
> > + struct page *page;
> > + int ret;
> > +
> > + page = region->pages[page_offset];
> > + if (!page)
> > + return -EINVAL;
> > +
> > + page_order = folio_order(page_folio(page));
> > + /* 1G huge pages aren't supported by the hypercalls */
> > + if (page_order == PUD_ORDER)
> > + return -EINVAL;
>
> In the general case, folio_order() could return a variety of values ranging from
> 0 up to at least PUD_ORDER. For example, "2" would be valid value in file
> system code that uses folios to do I/O in 16K blocks instead of just 4K blocks.
> Since this function is trying to find contiguous chunks of either single pages
> or 2M huge pages, I think you are expecting only three possible values: 0,
> PMD_ORDER, or PUD_ORDER. But do you know that "2" (for example)
> would never be returned? The memory involved here is populated using
> pin_user_pages_fast() for the pinned case, or using hmm_range_fault() for
> the movable case. I don't know mm behavior well enough to know if those
> functions could ever populate with a folio with an order other than 0 or
> PMD_ORDER. If such a folio could ever be used, then the way you check
> for a page size change won't be valid. For purposes of informing Hyper-V
> about 2 Meg pages, folio orders 0 through 8 are all equivalent, with folio
> order 9 (PMD_ORDER) being the marker for the start of 2 Meg large page.
>
> Somebody who knows mm behavior better than I do should comment. Or
> maybe you could just be more defensive and handle the case of folio orders
> not equal to 0 or PMD_ORDER.
>
Thanks for the comment.
This is addressed this in v9 by expclitly checking for 0 and HUGE_PMD_ORDER.
> > +
> > + stride = 1 << page_order;
> > +
> > + /* Start at stride since the first page is validated */
> > + for (count = stride; count < page_count; count += stride) {
>
> This striding doesn't work properly in the general case. Suppose the
> page_offset value puts the start of the chunk in the middle of a 2 Meg
> page, and that 2 Meg page is then followed by a bunch of single pages.
> (Presumably the mmu notifier "invalidate" callback could do this.)
> The use of the full stride here jumps over the remaining portion of the
> 2 Meg page plus some number of the single pages, which isn't what you
> want. For the striding to work, it must figure out how much remains in the
> initial large page, and then once the striding is aligned to the large page
> boundaries, the full stride length works.
>
> Also, what do the hypercalls in the handler functions do if a chunk starts
> in the middle of a 2 Meg page? It looks like the handler functions will set
> the *_LARGE_PAGE flag to the hypercall but then the hv_call_* function
> will fail if the page_count isn't 2 Meg aligned.
>
This situation you described is not possible, because invalidation
callback simply can't invalidate a part of the huge page even in THP
case (leave aside hugetlb case) without splitting it beforehand, and
splitting a huge page requires invalidation of the whole huge page
first.
> > + page = region->pages[page_offset + count];
> > +
> > + /* Break if current page is not present */
> > + if (!page)
> > + break;
> > +
> > + /* Break if page size changes */
> > + if (page_order != folio_order(page_folio(page)))
> > + break;
> > + }
> > +
> > + ret = handler(region, flags, page_offset, count);
> > + if (ret)
> > + return ret;
> > +
> > + return count;
> > +}
> > +
> > +/**
> > + * mshv_region_process_range - Processes a range of memory pages in a
> > + * region.
> > + * @region : Pointer to the memory region structure.
> > + * @flags : Flags to pass to the handler.
> > + * @page_offset: Offset into the region's pages array to start processing.
> > + * @page_count : Number of pages to process.
> > + * @handler : Callback function to handle each chunk of contiguous
> > + * pages.
> > + *
> > + * Iterates over the specified range of pages in @region, skipping
> > + * non-present pages. For each contiguous chunk of present pages, invokes
> > + * @handler via mshv_region_process_chunk.
> > + *
> > + * Note: The @handler callback must be able to handle both normal and huge
> > + * pages.
> > + *
> > + * Returns 0 on success, or a negative error code on failure.
> > + */
> > +static int mshv_region_process_range(struct mshv_mem_region *region,
> > + u32 flags,
> > + u64 page_offset, u64 page_count,
> > + int (*handler)(struct mshv_mem_region *region,
> > + u32 flags,
> > + u64 page_offset,
> > + u64 page_count))
> > +{
> > + long ret;
> > +
> > + if (page_offset + page_count > region->nr_pages)
> > + return -EINVAL;
> > +
> > + while (page_count) {
> > + /* Skip non-present pages */
> > + if (!region->pages[page_offset]) {
> > + page_offset++;
> > + page_count--;
> > + continue;
> > + }
> > +
> > + ret = mshv_region_process_chunk(region, flags,
> > + page_offset,
> > + page_count,
> > + handler);
> > + if (ret < 0)
> > + return ret;
> > +
> > + page_offset += ret;
> > + page_count -= ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
> > u64 uaddr, u32 flags,
> > bool is_mmio)
> > @@ -33,55 +151,80 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
> > if (flags & BIT(MSHV_SET_MEM_BIT_EXECUTABLE))
> > region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
> >
> > - /* Note: large_pages flag populated when we pin the pages */
> > if (!is_mmio)
> > region->flags.range_pinned = true;
> >
> > return region;
> > }
> >
> > +static int mshv_region_chunk_share(struct mshv_mem_region *region,
> > + u32 flags,
> > + u64 page_offset, u64 page_count)
> > +{
> > + if (PageTransCompound(region->pages[page_offset]))
> > + flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
>
> mshv_region_process_chunk() uses folio_size() to detect single pages vs. 2 Meg
> large pages. Here you are using PageTransCompound(). Any reason for the
> difference? This may be perfectly OK, but my knowledge of mm is too limited to
> know for sure. Looking at the implementations of folio_size() and
> PageTransCompound(), they seem to be looking at different fields in the struct page,
> and I don't know if the different fields are always in sync. Another case for someone
> with mm expertise to review carefully ....
>
Indeed, folio_order could be used here as well PageTransCompound could
be used in the chunk processing function (but then the size of the page
would still needed to be checked).
On the other hand, there is subtle difference between the chunk
procesing function and the callback in calls: the latter doesn't
validate the input, thus the chunk processing function should.
Thanks,
Stanislav
> Michael
>
> > +
> > + return hv_call_modify_spa_host_access(region->partition->pt_id,
> > + region->pages + page_offset,
> > + page_count,
> > + HV_MAP_GPA_READABLE |
> > + HV_MAP_GPA_WRITABLE,
> > + flags, true);
> > +}
> > +
> > int mshv_region_share(struct mshv_mem_region *region)
> > {
> > u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
> >
> > - if (region->flags.large_pages)
> > + return mshv_region_process_range(region, flags,
> > + 0, region->nr_pages,
> > + mshv_region_chunk_share);
> > +}
> > +
> > +static int mshv_region_chunk_unshare(struct mshv_mem_region *region,
> > + u32 flags,
> > + u64 page_offset, u64 page_count)
> > +{
> > + if (PageTransCompound(region->pages[page_offset]))
> > flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
> >
> > return hv_call_modify_spa_host_access(region->partition->pt_id,
> > - region->pages, region->nr_pages,
> > - HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE,
> > - flags, true);
> > + region->pages + page_offset,
> > + page_count, 0,
> > + flags, false);
> > }
> >
> > int mshv_region_unshare(struct mshv_mem_region *region)
> > {
> > u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE;
> >
> > - if (region->flags.large_pages)
> > - flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
> > -
> > - return hv_call_modify_spa_host_access(region->partition->pt_id,
> > - region->pages, region->nr_pages,
> > - 0,
> > - flags, false);
> > + return mshv_region_process_range(region, flags,
> > + 0, region->nr_pages,
> > + mshv_region_chunk_unshare);
> > }
> >
> > -static int mshv_region_remap_pages(struct mshv_mem_region *region,
> > - u32 map_flags,
> > +static int mshv_region_chunk_remap(struct mshv_mem_region *region,
> > + u32 flags,
> > u64 page_offset, u64 page_count)
> > {
> > - if (page_offset + page_count > region->nr_pages)
> > - return -EINVAL;
> > -
> > - if (region->flags.large_pages)
> > - map_flags |= HV_MAP_GPA_LARGE_PAGE;
> > + if (PageTransCompound(region->pages[page_offset]))
> > + flags |= HV_MAP_GPA_LARGE_PAGE;
> >
> > return hv_call_map_gpa_pages(region->partition->pt_id,
> > region->start_gfn + page_offset,
> > - page_count, map_flags,
> > + page_count, flags,
> > region->pages + page_offset);
> > }
> >
> > +static int mshv_region_remap_pages(struct mshv_mem_region *region,
> > + u32 map_flags,
> > + u64 page_offset, u64 page_count)
> > +{
> > + return mshv_region_process_range(region, map_flags,
> > + page_offset, page_count,
> > + mshv_region_chunk_remap);
> > +}
> > +
> > int mshv_region_map(struct mshv_mem_region *region)
> > {
> > u32 map_flags = region->hv_map_flags;
> > @@ -134,9 +277,6 @@ int mshv_region_pin(struct mshv_mem_region *region)
> > goto release_pages;
> > }
> >
> > - if (PageHuge(region->pages[0]))
> > - region->flags.large_pages = true;
> > -
> > return 0;
> >
> > release_pages:
> > @@ -144,10 +284,28 @@ int mshv_region_pin(struct mshv_mem_region *region)
> > return ret;
> > }
> >
> > +static int mshv_region_chunk_unmap(struct mshv_mem_region *region,
> > + u32 flags,
> > + u64 page_offset, u64 page_count)
> > +{
> > + if (PageTransCompound(region->pages[page_offset]))
> > + flags |= HV_UNMAP_GPA_LARGE_PAGE;
> > +
> > + return hv_call_unmap_gpa_pages(region->partition->pt_id,
> > + region->start_gfn + page_offset,
> > + page_count, 0);
> > +}
> > +
> > +static int mshv_region_unmap(struct mshv_mem_region *region)
> > +{
> > + return mshv_region_process_range(region, 0,
> > + 0, region->nr_pages,
> > + mshv_region_chunk_unmap);
> > +}
> > +
> > void mshv_region_destroy(struct mshv_mem_region *region)
> > {
> > struct mshv_partition *partition = region->partition;
> > - u32 unmap_flags = 0;
> > int ret;
> >
> > hlist_del(®ion->hnode);
> > @@ -162,12 +320,7 @@ void mshv_region_destroy(struct mshv_mem_region *region)
> > }
> > }
> >
> > - if (region->flags.large_pages)
> > - unmap_flags |= HV_UNMAP_GPA_LARGE_PAGE;
> > -
> > - /* ignore unmap failures and continue as process may be exiting */
> > - hv_call_unmap_gpa_pages(partition->pt_id, region->start_gfn,
> > - region->nr_pages, unmap_flags);
> > + mshv_region_unmap(region);
> >
> > mshv_region_invalidate(region);
> >
> > diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
> > index 0366f416c2f0..ff3374f13691 100644
> > --- a/drivers/hv/mshv_root.h
> > +++ b/drivers/hv/mshv_root.h
> > @@ -77,9 +77,8 @@ struct mshv_mem_region {
> > u64 start_uaddr;
> > u32 hv_map_flags;
> > struct {
> > - u64 large_pages: 1; /* 2MiB */
> > u64 range_pinned: 1;
> > - u64 reserved: 62;
> > + u64 reserved: 63;
> > } flags;
> > struct mshv_partition *partition;
> > struct page *pages[];
> >
> >
>
^ permalink raw reply
* Re: [PATCH 3/3] mshv: Add debugfs to view hypervisor statistics
From: Nuno Das Neves @ 2025-12-04 18:57 UTC (permalink / raw)
To: Stanislav Kinsburskii
Cc: linux-hyperv, linux-kernel, kys, haiyangz, wei.liu, decui, longli,
mhklinux, prapal, mrathor, paekkaladevi, Jinank Jain
In-Reply-To: <aTCNoecOOFHabyZC@skinsburskii.localdomain>
On 12/3/2025 11:21 AM, Stanislav Kinsburskii wrote:
> On Wed, Dec 03, 2025 at 09:53:25AM -0800, Nuno Das Neves wrote:
>> Introduce a debugfs interface to expose root and child partition stats
>> when running with mshv_root.
>>
>
> <snip>
>> +static void mshv_debugfs_parent_partition_remove(void)
>
> nit: it makes sense to add __exit to this method.
>
> <snip>
>
>> +
>> +void mshv_debugfs_exit(void)
>
> Ditto.
It looks like __exit is purposely omitted here, because
mshv_debugfs_exit() is called in the error cleanup path of
mshv_parent_partition_init().
Thanks
Nuno
>
> Reviewed-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
>
> Thanks,
> Stanislav
^ permalink raw reply
* RE: [PATCH v7 6/7] Drivers: hv: Add refcount and locking to mem regions
From: Michael Kelley @ 2025-12-04 16:48 UTC (permalink / raw)
To: Stanislav Kinsburskii, kys@microsoft.com, haiyangz@microsoft.com,
wei.liu@kernel.org, decui@microsoft.com
Cc: linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <176412296278.447063.4767524278636692490.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>
From: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com> Sent: Tuesday, November 25, 2025 6:09 PM
>
> Introduce kref-based reference counting and spinlock protection for
> memory regions in Hyper-V partition management. This change improves
> memory region lifecycle management and ensures thread-safe access to the
> region list.
>
> Also improves the check for overlapped memory regions during region
> creation, preventing duplicate or conflicting mappings.
This paragraph seems spurious. I think it applies to what's in Patch 5 of this
series.
>
> Previously, the regions list was protected by the partition mutex.
> However, this approach is too heavy for frequent fault and invalidation
> operations. Finer grained locking is now used to improve efficiency and
> concurrency.
>
> This is a precursor to supporting movable memory regions. Fault and
> invalidation handling for movable regions will require safe traversal of
> the region list and holding a region reference while performing
> invalidation or fault operations.
The commit message discussion about the need for the refcounting and
locking seemed a bit vague to me. It wasn't entirely clear whether these
changes are bug fixing existing race conditions, or whether they are new
functionality to support movable regions.
In looking at the existing code, it seems that the main serialization mechanisms
are that partition ioctls are serialized on pt_mutex, and VP ioctls are serialized
on vp_mutex (though multiple VP ioctls can be in progress simultaneously
against different VPs). The serialization of partition ioctls ensures that region
manipulation is serialized, and that, for example, two region creations can't
both verify that there's no overlap, but then overlap with each other. And
region creation and deletion are serialized. In current code, the VP ioctls don't
look at the region data structures, so there can't be any races between
partition and VP ioctls (which are not serialized with each other). The only
question I had about existing code is the mshv_partition_release() function,
which proceeds without serializing against any partition ioctls, but maybe
higher-level file system code ensures that no ioctls are in progress before
the .release callback is made.
The new requirement is movable regions, where the VP ioctl MSHV_RUN_VP
needs to look at region data structures. You've said that in the last paragraph
of your commit message. So I'm reading this as that the new locking is
needed specifically because multiple MSHV_RUN_VP ioctls will likely be
in flight simultaneously, and they are not currently serialized with the
region operations initiated by partition ioctls. And then there are the
"invalidate" callbacks that are running on some other kernel thread and
which also needs synchronization to do region manipulation.
Maybe I'm just looking for a little bit of a written "road map" somewhere
that describes the intended locking scheme at a high level. :-)
Michael
>
> Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
> ---
> drivers/hv/mshv_regions.c | 19 ++++++++++++++++---
> drivers/hv/mshv_root.h | 6 +++++-
> drivers/hv/mshv_root_main.c | 34 ++++++++++++++++++++++++++--------
> 3 files changed, 47 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
> index d535d2e3e811..6450a7ed8493 100644
> --- a/drivers/hv/mshv_regions.c
> +++ b/drivers/hv/mshv_regions.c
> @@ -7,6 +7,7 @@
> * Authors: Microsoft Linux virtualization team
> */
>
> +#include <linux/kref.h>
> #include <linux/mm.h>
> #include <linux/vmalloc.h>
>
> @@ -154,6 +155,8 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
> if (!is_mmio)
> region->flags.range_pinned = true;
>
> + kref_init(®ion->refcount);
> +
> return region;
> }
>
> @@ -303,13 +306,13 @@ static int mshv_region_unmap(struct mshv_mem_region *region)
> mshv_region_chunk_unmap);
> }
>
> -void mshv_region_destroy(struct mshv_mem_region *region)
> +static void mshv_region_destroy(struct kref *ref)
> {
> + struct mshv_mem_region *region =
> + container_of(ref, struct mshv_mem_region, refcount);
> struct mshv_partition *partition = region->partition;
> int ret;
>
> - hlist_del(®ion->hnode);
> -
> if (mshv_partition_encrypted(partition)) {
> ret = mshv_region_share(region);
> if (ret) {
> @@ -326,3 +329,13 @@ void mshv_region_destroy(struct mshv_mem_region *region)
>
> vfree(region);
> }
> +
> +void mshv_region_put(struct mshv_mem_region *region)
> +{
> + kref_put(®ion->refcount, mshv_region_destroy);
> +}
> +
> +int mshv_region_get(struct mshv_mem_region *region)
> +{
> + return kref_get_unless_zero(®ion->refcount);
> +}
> diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
> index ff3374f13691..4249534ba900 100644
> --- a/drivers/hv/mshv_root.h
> +++ b/drivers/hv/mshv_root.h
> @@ -72,6 +72,7 @@ do { \
>
> struct mshv_mem_region {
> struct hlist_node hnode;
> + struct kref refcount;
> u64 nr_pages;
> u64 start_gfn;
> u64 start_uaddr;
> @@ -97,6 +98,8 @@ struct mshv_partition {
> u64 pt_id;
> refcount_t pt_ref_count;
> struct mutex pt_mutex;
> +
> + spinlock_t pt_mem_regions_lock;
> struct hlist_head pt_mem_regions; // not ordered
>
> u32 pt_vp_count;
> @@ -319,6 +322,7 @@ int mshv_region_unshare(struct mshv_mem_region *region);
> int mshv_region_map(struct mshv_mem_region *region);
> void mshv_region_invalidate(struct mshv_mem_region *region);
> int mshv_region_pin(struct mshv_mem_region *region);
> -void mshv_region_destroy(struct mshv_mem_region *region);
> +void mshv_region_put(struct mshv_mem_region *region);
> +int mshv_region_get(struct mshv_mem_region *region);
>
> #endif /* _MSHV_ROOT_H_ */
> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
> index ae600b927f49..1ef2a28beb17 100644
> --- a/drivers/hv/mshv_root_main.c
> +++ b/drivers/hv/mshv_root_main.c
> @@ -1086,9 +1086,13 @@ static int mshv_partition_create_region(struct mshv_partition *partition,
> u64 nr_pages = HVPFN_DOWN(mem->size);
>
> /* Reject overlapping regions */
> + spin_lock(&partition->pt_mem_regions_lock);
> if (mshv_partition_region_by_gfn(partition, mem->guest_pfn) ||
> - mshv_partition_region_by_gfn(partition, mem->guest_pfn + nr_pages - 1))
> + mshv_partition_region_by_gfn(partition, mem->guest_pfn + nr_pages - 1)) {
> + spin_unlock(&partition->pt_mem_regions_lock);
> return -EEXIST;
> + }
> + spin_unlock(&partition->pt_mem_regions_lock);
>
> rg = mshv_region_create(mem->guest_pfn, nr_pages,
> mem->userspace_addr, mem->flags,
> @@ -1220,8 +1224,9 @@ mshv_map_user_memory(struct mshv_partition *partition,
> if (ret)
> goto errout;
>
> - /* Install the new region */
> + spin_lock(&partition->pt_mem_regions_lock);
> hlist_add_head(®ion->hnode, &partition->pt_mem_regions);
> + spin_unlock(&partition->pt_mem_regions_lock);
>
> return 0;
>
> @@ -1240,17 +1245,27 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
> if (!(mem.flags & BIT(MSHV_SET_MEM_BIT_UNMAP)))
> return -EINVAL;
>
> + spin_lock(&partition->pt_mem_regions_lock);
> +
> region = mshv_partition_region_by_gfn(partition, mem.guest_pfn);
> - if (!region)
> - return -EINVAL;
> + if (!region) {
> + spin_unlock(&partition->pt_mem_regions_lock);
> + return -ENOENT;
> + }
>
> /* Paranoia check */
> if (region->start_uaddr != mem.userspace_addr ||
> region->start_gfn != mem.guest_pfn ||
> - region->nr_pages != HVPFN_DOWN(mem.size))
> + region->nr_pages != HVPFN_DOWN(mem.size)) {
> + spin_unlock(&partition->pt_mem_regions_lock);
> return -EINVAL;
> + }
> +
> + hlist_del(®ion->hnode);
>
> - mshv_region_destroy(region);
> + spin_unlock(&partition->pt_mem_regions_lock);
> +
> + mshv_region_put(region);
>
> return 0;
> }
> @@ -1653,8 +1668,10 @@ static void destroy_partition(struct mshv_partition *partition)
> remove_partition(partition);
>
> hlist_for_each_entry_safe(region, n, &partition->pt_mem_regions,
> - hnode)
> - mshv_region_destroy(region);
> + hnode) {
> + hlist_del(®ion->hnode);
> + mshv_region_put(region);
> + }
>
> /* Withdraw and free all pages we deposited */
> hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, partition->pt_id);
> @@ -1852,6 +1869,7 @@ mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev)
>
> INIT_HLIST_HEAD(&partition->pt_devices);
>
> + spin_lock_init(&partition->pt_mem_regions_lock);
> INIT_HLIST_HEAD(&partition->pt_mem_regions);
>
> mshv_eventfd_init(partition);
>
>
^ permalink raw reply
* RE: [PATCH v7 4/7] Drivers: hv: Fix huge page handling in memory region traversal
From: Michael Kelley @ 2025-12-04 16:03 UTC (permalink / raw)
To: Stanislav Kinsburskii, kys@microsoft.com, haiyangz@microsoft.com,
wei.liu@kernel.org, decui@microsoft.com
Cc: linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <176412295155.447063.16512843211428609586.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>
From: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com> Sent: Tuesday, November 25, 2025 6:09 PM
>
> The previous code assumed that if a region's first page was huge, the
> entire region consisted of huge pages and stored this in a large_pages
> flag. This premise is incorrect not only for movable regions (where
> pages can be split and merged on invalidate callbacks or page faults),
> but even for pinned regions: THPs can be split and merged during
> allocation, so a large, pinned region may contain a mix of huge and
> regular pages.
>
> This change removes the large_pages flag and replaces region-wide
> assumptions with per-chunk inspection of the actual page size when
> mapping, unmapping, sharing, and unsharing. This makes huge page
> handling correct for mixed-page regions and avoids relying on stale
> metadata that can easily become invalid as memory is remapped.
>
> Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
> ---
> drivers/hv/mshv_regions.c | 213 +++++++++++++++++++++++++++++++++++++++-
> -----
> drivers/hv/mshv_root.h | 3 -
> 2 files changed, 184 insertions(+), 32 deletions(-)
>
> diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
> index 35b866670840..d535d2e3e811 100644
> --- a/drivers/hv/mshv_regions.c
> +++ b/drivers/hv/mshv_regions.c
> @@ -14,6 +14,124 @@
>
> #include "mshv_root.h"
>
> +/**
> + * mshv_region_process_chunk - Processes a contiguous chunk of memory pages
> + * in a region.
> + * @region : Pointer to the memory region structure.
> + * @flags : Flags to pass to the handler.
> + * @page_offset: Offset into the region's pages array to start processing.
> + * @page_count : Number of pages to process.
> + * @handler : Callback function to handle the chunk.
> + *
> + * This function scans the region's pages starting from @page_offset,
> + * checking for contiguous present pages of the same size (normal or huge).
> + * It invokes @handler for the chunk of contiguous pages found. Returns the
> + * number of pages handled, or a negative error code if the first page is
> + * not present or the handler fails.
> + *
> + * Note: The @handler callback must be able to handle both normal and huge
> + * pages.
> + *
> + * Return: Number of pages handled, or negative error code.
> + */
> +static long mshv_region_process_chunk(struct mshv_mem_region *region,
> + u32 flags,
> + u64 page_offset, u64 page_count,
> + int (*handler)(struct mshv_mem_region *region,
> + u32 flags,
> + u64 page_offset,
> + u64 page_count))
> +{
> + u64 count, stride;
> + unsigned int page_order;
> + struct page *page;
> + int ret;
> +
> + page = region->pages[page_offset];
> + if (!page)
> + return -EINVAL;
> +
> + page_order = folio_order(page_folio(page));
> + /* 1G huge pages aren't supported by the hypercalls */
> + if (page_order == PUD_ORDER)
> + return -EINVAL;
In the general case, folio_order() could return a variety of values ranging from
0 up to at least PUD_ORDER. For example, "2" would be valid value in file
system code that uses folios to do I/O in 16K blocks instead of just 4K blocks.
Since this function is trying to find contiguous chunks of either single pages
or 2M huge pages, I think you are expecting only three possible values: 0,
PMD_ORDER, or PUD_ORDER. But do you know that "2" (for example)
would never be returned? The memory involved here is populated using
pin_user_pages_fast() for the pinned case, or using hmm_range_fault() for
the movable case. I don't know mm behavior well enough to know if those
functions could ever populate with a folio with an order other than 0 or
PMD_ORDER. If such a folio could ever be used, then the way you check
for a page size change won't be valid. For purposes of informing Hyper-V
about 2 Meg pages, folio orders 0 through 8 are all equivalent, with folio
order 9 (PMD_ORDER) being the marker for the start of 2 Meg large page.
Somebody who knows mm behavior better than I do should comment. Or
maybe you could just be more defensive and handle the case of folio orders
not equal to 0 or PMD_ORDER.
> +
> + stride = 1 << page_order;
> +
> + /* Start at stride since the first page is validated */
> + for (count = stride; count < page_count; count += stride) {
This striding doesn't work properly in the general case. Suppose the
page_offset value puts the start of the chunk in the middle of a 2 Meg
page, and that 2 Meg page is then followed by a bunch of single pages.
(Presumably the mmu notifier "invalidate" callback could do this.)
The use of the full stride here jumps over the remaining portion of the
2 Meg page plus some number of the single pages, which isn't what you
want. For the striding to work, it must figure out how much remains in the
initial large page, and then once the striding is aligned to the large page
boundaries, the full stride length works.
Also, what do the hypercalls in the handler functions do if a chunk starts
in the middle of a 2 Meg page? It looks like the handler functions will set
the *_LARGE_PAGE flag to the hypercall but then the hv_call_* function
will fail if the page_count isn't 2 Meg aligned.
> + page = region->pages[page_offset + count];
> +
> + /* Break if current page is not present */
> + if (!page)
> + break;
> +
> + /* Break if page size changes */
> + if (page_order != folio_order(page_folio(page)))
> + break;
> + }
> +
> + ret = handler(region, flags, page_offset, count);
> + if (ret)
> + return ret;
> +
> + return count;
> +}
> +
> +/**
> + * mshv_region_process_range - Processes a range of memory pages in a
> + * region.
> + * @region : Pointer to the memory region structure.
> + * @flags : Flags to pass to the handler.
> + * @page_offset: Offset into the region's pages array to start processing.
> + * @page_count : Number of pages to process.
> + * @handler : Callback function to handle each chunk of contiguous
> + * pages.
> + *
> + * Iterates over the specified range of pages in @region, skipping
> + * non-present pages. For each contiguous chunk of present pages, invokes
> + * @handler via mshv_region_process_chunk.
> + *
> + * Note: The @handler callback must be able to handle both normal and huge
> + * pages.
> + *
> + * Returns 0 on success, or a negative error code on failure.
> + */
> +static int mshv_region_process_range(struct mshv_mem_region *region,
> + u32 flags,
> + u64 page_offset, u64 page_count,
> + int (*handler)(struct mshv_mem_region *region,
> + u32 flags,
> + u64 page_offset,
> + u64 page_count))
> +{
> + long ret;
> +
> + if (page_offset + page_count > region->nr_pages)
> + return -EINVAL;
> +
> + while (page_count) {
> + /* Skip non-present pages */
> + if (!region->pages[page_offset]) {
> + page_offset++;
> + page_count--;
> + continue;
> + }
> +
> + ret = mshv_region_process_chunk(region, flags,
> + page_offset,
> + page_count,
> + handler);
> + if (ret < 0)
> + return ret;
> +
> + page_offset += ret;
> + page_count -= ret;
> + }
> +
> + return 0;
> +}
> +
> struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
> u64 uaddr, u32 flags,
> bool is_mmio)
> @@ -33,55 +151,80 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
> if (flags & BIT(MSHV_SET_MEM_BIT_EXECUTABLE))
> region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
>
> - /* Note: large_pages flag populated when we pin the pages */
> if (!is_mmio)
> region->flags.range_pinned = true;
>
> return region;
> }
>
> +static int mshv_region_chunk_share(struct mshv_mem_region *region,
> + u32 flags,
> + u64 page_offset, u64 page_count)
> +{
> + if (PageTransCompound(region->pages[page_offset]))
> + flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
mshv_region_process_chunk() uses folio_size() to detect single pages vs. 2 Meg
large pages. Here you are using PageTransCompound(). Any reason for the
difference? This may be perfectly OK, but my knowledge of mm is too limited to
know for sure. Looking at the implementations of folio_size() and
PageTransCompound(), they seem to be looking at different fields in the struct page,
and I don't know if the different fields are always in sync. Another case for someone
with mm expertise to review carefully ....
Michael
> +
> + return hv_call_modify_spa_host_access(region->partition->pt_id,
> + region->pages + page_offset,
> + page_count,
> + HV_MAP_GPA_READABLE |
> + HV_MAP_GPA_WRITABLE,
> + flags, true);
> +}
> +
> int mshv_region_share(struct mshv_mem_region *region)
> {
> u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
>
> - if (region->flags.large_pages)
> + return mshv_region_process_range(region, flags,
> + 0, region->nr_pages,
> + mshv_region_chunk_share);
> +}
> +
> +static int mshv_region_chunk_unshare(struct mshv_mem_region *region,
> + u32 flags,
> + u64 page_offset, u64 page_count)
> +{
> + if (PageTransCompound(region->pages[page_offset]))
> flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
>
> return hv_call_modify_spa_host_access(region->partition->pt_id,
> - region->pages, region->nr_pages,
> - HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE,
> - flags, true);
> + region->pages + page_offset,
> + page_count, 0,
> + flags, false);
> }
>
> int mshv_region_unshare(struct mshv_mem_region *region)
> {
> u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE;
>
> - if (region->flags.large_pages)
> - flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
> -
> - return hv_call_modify_spa_host_access(region->partition->pt_id,
> - region->pages, region->nr_pages,
> - 0,
> - flags, false);
> + return mshv_region_process_range(region, flags,
> + 0, region->nr_pages,
> + mshv_region_chunk_unshare);
> }
>
> -static int mshv_region_remap_pages(struct mshv_mem_region *region,
> - u32 map_flags,
> +static int mshv_region_chunk_remap(struct mshv_mem_region *region,
> + u32 flags,
> u64 page_offset, u64 page_count)
> {
> - if (page_offset + page_count > region->nr_pages)
> - return -EINVAL;
> -
> - if (region->flags.large_pages)
> - map_flags |= HV_MAP_GPA_LARGE_PAGE;
> + if (PageTransCompound(region->pages[page_offset]))
> + flags |= HV_MAP_GPA_LARGE_PAGE;
>
> return hv_call_map_gpa_pages(region->partition->pt_id,
> region->start_gfn + page_offset,
> - page_count, map_flags,
> + page_count, flags,
> region->pages + page_offset);
> }
>
> +static int mshv_region_remap_pages(struct mshv_mem_region *region,
> + u32 map_flags,
> + u64 page_offset, u64 page_count)
> +{
> + return mshv_region_process_range(region, map_flags,
> + page_offset, page_count,
> + mshv_region_chunk_remap);
> +}
> +
> int mshv_region_map(struct mshv_mem_region *region)
> {
> u32 map_flags = region->hv_map_flags;
> @@ -134,9 +277,6 @@ int mshv_region_pin(struct mshv_mem_region *region)
> goto release_pages;
> }
>
> - if (PageHuge(region->pages[0]))
> - region->flags.large_pages = true;
> -
> return 0;
>
> release_pages:
> @@ -144,10 +284,28 @@ int mshv_region_pin(struct mshv_mem_region *region)
> return ret;
> }
>
> +static int mshv_region_chunk_unmap(struct mshv_mem_region *region,
> + u32 flags,
> + u64 page_offset, u64 page_count)
> +{
> + if (PageTransCompound(region->pages[page_offset]))
> + flags |= HV_UNMAP_GPA_LARGE_PAGE;
> +
> + return hv_call_unmap_gpa_pages(region->partition->pt_id,
> + region->start_gfn + page_offset,
> + page_count, 0);
> +}
> +
> +static int mshv_region_unmap(struct mshv_mem_region *region)
> +{
> + return mshv_region_process_range(region, 0,
> + 0, region->nr_pages,
> + mshv_region_chunk_unmap);
> +}
> +
> void mshv_region_destroy(struct mshv_mem_region *region)
> {
> struct mshv_partition *partition = region->partition;
> - u32 unmap_flags = 0;
> int ret;
>
> hlist_del(®ion->hnode);
> @@ -162,12 +320,7 @@ void mshv_region_destroy(struct mshv_mem_region *region)
> }
> }
>
> - if (region->flags.large_pages)
> - unmap_flags |= HV_UNMAP_GPA_LARGE_PAGE;
> -
> - /* ignore unmap failures and continue as process may be exiting */
> - hv_call_unmap_gpa_pages(partition->pt_id, region->start_gfn,
> - region->nr_pages, unmap_flags);
> + mshv_region_unmap(region);
>
> mshv_region_invalidate(region);
>
> diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
> index 0366f416c2f0..ff3374f13691 100644
> --- a/drivers/hv/mshv_root.h
> +++ b/drivers/hv/mshv_root.h
> @@ -77,9 +77,8 @@ struct mshv_mem_region {
> u64 start_uaddr;
> u32 hv_map_flags;
> struct {
> - u64 large_pages: 1; /* 2MiB */
> u64 range_pinned: 1;
> - u64 reserved: 62;
> + u64 reserved: 63;
> } flags;
> struct mshv_partition *partition;
> struct page *pages[];
>
>
^ permalink raw reply
* Re: [RFC PATCH] Drivers: hv: Confidential VMBus exernal memory support
From: Tianyu Lan @ 2025-12-04 11:35 UTC (permalink / raw)
To: Michael Kelley
Cc: Christoph Hellwig, Robin Murphy, kys@microsoft.com,
haiyangz@microsoft.com, wei.liu@kernel.org, decui@microsoft.com,
longli@microsoft.com, vdso@hexbites.dev, Tianyu Lan,
linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <SN6PR02MB4157FB57619785BAA50B3586D4A6A@SN6PR02MB4157.namprd02.prod.outlook.com>
On Thu, Dec 4, 2025 at 11:35 AM Michael Kelley <mhklinux@outlook.com> wrote:
>
> From: Tianyu Lan <ltykernel@gmail.com> Sent: Wednesday, December 3, 2025 6:21 AM
> >
> > On Sat, Nov 29, 2025 at 1:47 AM Michael Kelley <mhklinux@outlook.com> wrote:
> > >
> > > From: Tianyu Lan <ltykernel@gmail.com> Sent: Monday, November 24, 2025 10:29 AM
>
> [snip]
>
> > >
> > > Here's my idea for an alternate approach. The goal is to allow use of the
> > > swiotlb to be disabled on a per-device basis. A device is initialized for swiotlb
> > > usage by swiotlb_dev_init(), which sets dev->dma_io_tlb_mem to point to the
> > > default swiotlb memory. For VMBus devices, the calling sequence is
> > > vmbus_device_register() -> device_register() -> device_initialize() ->
> > > swiotlb_dev_init(). But if vmbus_device_register() could override the
> > > dev->dma_io_tlb_mem value and put it back to NULL, swiotlb operations
> > > would be disabled on the device. Furthermore, is_swiotlb_force_bounce()
> > > would return "false", and the normal DMA functions would not force the
> > > use of bounce buffers. The entire code change looks like this:
> > >
> > > --- a/drivers/hv/vmbus_drv.c
> > > +++ b/drivers/hv/vmbus_drv.c
> > > @@ -2133,11 +2133,15 @@ int vmbus_device_register(struct hv_device *child_device_obj)
> > > child_device_obj->device.dma_mask = &child_device_obj->dma_mask;
> > > dma_set_mask(&child_device_obj->device, DMA_BIT_MASK(64));
> > >
> > > + device_initialize(&child_device_obj->device);
> > > + if (child_device_obj->channel->co_external_memory)
> > > + child_device_obj->device.dma_io_tlb_mem = NULL;
> > > +
> > > /*
> > > * Register with the LDM. This will kick off the driver/device
> > > * binding...which will eventually call vmbus_match() and vmbus_probe()
> > > */
> > > - ret = device_register(&child_device_obj->device);
> > > + ret = device_add(&child_device_obj->device);
> > > if (ret) {
> > > pr_err("Unable to register child device\n");
> > > put_device(&child_device_obj->device);
> > >
> > > I've only compile tested the above since I don't have an environment where
> > > I can test Confidential VMBus. You would need to verify whether my thinking
> > > is correct and this produces the intended result.
> >
> > Thanks Michael. I tested it and it seems to hit an issue. Will double check.with
> > HCL/paravisor team.
> >
> > We considered such a change before. From Roman's previous patch, it seems to
> > need to change phys_to_dma() and force_dma_unencrypted().
>
> In a Hyper-V SEV-SNP VM with a paravisor, I assert that phys_to_dma() and
> __phys_to_dma() do the same thing. phys_to_dma() calls dma_addr_encrypted(),
> which does __sme_set(). But in a Hyper-V VM using vTOM, sme_me_mask is
> always 0, so dma_addr_encrypted() is a no-op. dma_addr_unencrypted() and
> dma_addr_canonical() are also no-ops. See include/linux/mem_encrypt.h. So
> in a Hyper-V SEV-SNP VM, the DMA layer doesn't change anything related to
> encryption when translating between a physical address and a DMA address.
> Same thing is true for a Hyper-V TDX VM with paravisor.
>
> force_dma_unencrypted() will indeed return "true", and it is used in
> phys_to_dma_direct(). But both return paths in phys_to_dma_direct() return the
> same result because of dma_addr_unencrypted() and dma_addr_encrypted()
> being no-ops. Other uses of force_dma_unencrypted() are only in the
> dma_alloc_*() paths, but dma_alloc_*() isn't used by VMBus devices because
> the device control structures are in the ring buffer, which as you have noted, is
> already handled separately. So for the moment, I don't think the return value
> from force_dma_unencrypted() matters.
>
> So I'm guessing something else unexpected is happening such that just disabling
> the swiotlb on a per-device basis doesn't work. Assuming that Roman's original
> patch actually worked, I'm trying to figure out how my idea is different in a way
> that has a material effect on things. And if your patch works by going directly to
> __phys_to_dma(), it should also work when using phys_to_dma() instead.
>
I agree with your analysis and your proposal is much simpler. The
issue I hit was a lot
of user space tasks were blocked after FIO test of several minutes. I
also reproduced
with DMA ops patch after longer test and so need to double check
whether we missed
something or it's caused by other issue. If it's related with
private/shared address used
in the normal guest, we may debug in the paravisor.
--
Thanks
Tianyu Lan
^ permalink raw reply
* Re: [PATCH v3 0/9] arch,sysfb,efi: Support EDID on non-x86 EFI systems
From: Ard Biesheuvel @ 2025-12-04 9:17 UTC (permalink / raw)
To: Thomas Zimmermann
Cc: Richard Lyu, javierm, arnd, helgaas, x86, linux-arm-kernel,
linux-kernel, linux-efi, loongarch, linux-riscv, dri-devel,
linux-hyperv, linux-pci, linux-fbdev
In-Reply-To: <fc4ea259-3389-46e2-b860-972aa8179507@suse.de>
On Thu, 27 Nov 2025 at 08:43, Thomas Zimmermann <tzimmermann@suse.de> wrote:
>
> Hi
>
> Am 27.11.25 um 03:20 schrieb Richard Lyu:
> > Hi Thomas,
> >
> > I am attempting to test this patch series but encountered merge conflicts when applying it to various trees.
> > Could you please clarify the specific base commit (or branch/tag) this series was generated against?
>
> Thanks for testing.
>
> >
> > When testing on the next branch on commits 7a2ff00 and e41ef37, I hit a conflict on PATCH v3 4/9:
> > patching file drivers/pci/vgaarb.c
> > Hunk #2 FAILED at 557.
> > 1 out of 2 hunks FAILED -- rejects in file drivers/pci/vgaarb.c
> >
> > When testing against 3a86608 (Linux 6.18-rc1), the following conflicts occurred:
> > patching file drivers/gpu/drm/sysfb/efidrm.c
> > Hunk #1 FAILED at 24.
> > 1 out of 2 hunks FAILED -- rejects in file drivers/gpu/drm/sysfb/efidrm.c
> > patching file drivers/gpu/drm/sysfb/vesadrm.c
> > Hunk #1 FAILED at 25.
> > 1 out of 2 hunks FAILED -- rejects in file drivers/gpu/drm/sysfb/vesadrm.c
> >
> > Please let me know the correct base, and I will retest.
>
> It's in the cover letter: d724c6f85e80a23ed46b7ebc6e38b527c09d64f5 The
> commit is in linux-next. The idea is that the EFI tree can pick up the
> changes easily in the next cycle. linux-next seemed like the best
> choice. Best regards Thomas
Thanks. I will queue this up as soon as -rc1 is released.
^ permalink raw reply
* RE: [RFC PATCH] Drivers: hv: Confidential VMBus exernal memory support
From: Michael Kelley @ 2025-12-04 3:35 UTC (permalink / raw)
To: Tianyu Lan, Christoph Hellwig, Robin Murphy
Cc: kys@microsoft.com, haiyangz@microsoft.com, wei.liu@kernel.org,
decui@microsoft.com, longli@microsoft.com, vdso@hexbites.dev,
Tianyu Lan, linux-hyperv@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <CAMvTesCbNYHjiwaZC4EJopErZhW+vM0d87zJ54RT_AKXb-2yjw@mail.gmail.com>
From: Tianyu Lan <ltykernel@gmail.com> Sent: Wednesday, December 3, 2025 6:21 AM
>
> On Sat, Nov 29, 2025 at 1:47 AM Michael Kelley <mhklinux@outlook.com> wrote:
> >
> > From: Tianyu Lan <ltykernel@gmail.com> Sent: Monday, November 24, 2025 10:29 AM
[snip]
> >
> > Here's my idea for an alternate approach. The goal is to allow use of the
> > swiotlb to be disabled on a per-device basis. A device is initialized for swiotlb
> > usage by swiotlb_dev_init(), which sets dev->dma_io_tlb_mem to point to the
> > default swiotlb memory. For VMBus devices, the calling sequence is
> > vmbus_device_register() -> device_register() -> device_initialize() ->
> > swiotlb_dev_init(). But if vmbus_device_register() could override the
> > dev->dma_io_tlb_mem value and put it back to NULL, swiotlb operations
> > would be disabled on the device. Furthermore, is_swiotlb_force_bounce()
> > would return "false", and the normal DMA functions would not force the
> > use of bounce buffers. The entire code change looks like this:
> >
> > --- a/drivers/hv/vmbus_drv.c
> > +++ b/drivers/hv/vmbus_drv.c
> > @@ -2133,11 +2133,15 @@ int vmbus_device_register(struct hv_device *child_device_obj)
> > child_device_obj->device.dma_mask = &child_device_obj->dma_mask;
> > dma_set_mask(&child_device_obj->device, DMA_BIT_MASK(64));
> >
> > + device_initialize(&child_device_obj->device);
> > + if (child_device_obj->channel->co_external_memory)
> > + child_device_obj->device.dma_io_tlb_mem = NULL;
> > +
> > /*
> > * Register with the LDM. This will kick off the driver/device
> > * binding...which will eventually call vmbus_match() and vmbus_probe()
> > */
> > - ret = device_register(&child_device_obj->device);
> > + ret = device_add(&child_device_obj->device);
> > if (ret) {
> > pr_err("Unable to register child device\n");
> > put_device(&child_device_obj->device);
> >
> > I've only compile tested the above since I don't have an environment where
> > I can test Confidential VMBus. You would need to verify whether my thinking
> > is correct and this produces the intended result.
>
> Thanks Michael. I tested it and it seems to hit an issue. Will double check.with
> HCL/paravisor team.
>
> We considered such a change before. From Roman's previous patch, it seems to
> need to change phys_to_dma() and force_dma_unencrypted().
In a Hyper-V SEV-SNP VM with a paravisor, I assert that phys_to_dma() and
__phys_to_dma() do the same thing. phys_to_dma() calls dma_addr_encrypted(),
which does __sme_set(). But in a Hyper-V VM using vTOM, sme_me_mask is
always 0, so dma_addr_encrypted() is a no-op. dma_addr_unencrypted() and
dma_addr_canonical() are also no-ops. See include/linux/mem_encrypt.h. So
in a Hyper-V SEV-SNP VM, the DMA layer doesn't change anything related to
encryption when translating between a physical address and a DMA address.
Same thing is true for a Hyper-V TDX VM with paravisor.
force_dma_unencrypted() will indeed return "true", and it is used in
phys_to_dma_direct(). But both return paths in phys_to_dma_direct() return the
same result because of dma_addr_unencrypted() and dma_addr_encrypted()
being no-ops. Other uses of force_dma_unencrypted() are only in the
dma_alloc_*() paths, but dma_alloc_*() isn't used by VMBus devices because
the device control structures are in the ring buffer, which as you have noted, is
already handled separately. So for the moment, I don't think the return value
from force_dma_unencrypted() matters.
So I'm guessing something else unexpected is happening such that just disabling
the swiotlb on a per-device basis doesn't work. Assuming that Roman's original
patch actually worked, I'm trying to figure out how my idea is different in a way
that has a material effect on things. And if your patch works by going directly to
__phys_to_dma(), it should also work when using phys_to_dma() instead.
I will try a few experiments on a normal Confidential VM (i.e., without Confidential
VMBus) to confirm that my conclusions from reading the code really are correct.
FWIW, I'm looking at the linux-next20251119 code base.
Michael
>
> >
> > Directly setting dma_io_tlb_mem to NULL isn't great. It would be better
> > to add an exported function swiotlb_dev_disable() to swiotlb code that sets
> > dma_io_tlb_mem to NULL, but you get the idea.
> >
> > Other reviewers may still see this approach as a bit of a hack, but it's a lot
> > less of a hack than introducing Hyper-V specific DMA functions.
> > swiotlb_dev_disable() is conceptually needed for TDISP devices, as TDISP
> > devices must similarly protect confidentiality by not allowing use of the swiotlb.
> > So adding swiotlb_dev_disable() is a step in the right direction, even if the
> > eventual TDISP code does it slightly differently. Doing the disable on a
> > per-device basis is also the right thing in the long run.
> >
^ permalink raw reply
* Re: [PATCH v6 2/3] hyperv: Use reboot notifier to configure sleep state
From: Nuno Das Neves @ 2025-12-03 23:02 UTC (permalink / raw)
To: Praveen K Paladugu, kys, haiyangz, wei.liu, decui, tglx, mingo,
linux-hyperv, linux-kernel, bp, dave.hansen, x86, hpa, arnd
Cc: anbelski, easwar.hariharan, skinsburskii
In-Reply-To: <20251126215013.11549-3-prapal@linux.microsoft.com>
On 11/26/2025 1:49 PM, Praveen K Paladugu wrote:
> Configure sleep state information in mshv hypervisor. This sleep state
> information from ACPI will be used by hypervisor to poweroff the host.
>
> Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
> Co-developed-by: Anatol Belski <anbelski@linux.microsoft.com>
> Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
> Reviewed-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
> ---
> arch/x86/hyperv/hv_init.c | 1 +
> arch/x86/include/asm/mshyperv.h | 2 +
> drivers/hv/mshv_common.c | 80 +++++++++++++++++++++++++++++++++
> 3 files changed, 83 insertions(+)
>
As Michael mentioned, please fix the bug in the Makefile, and clean up
the unnecessary function stubs. Otherwise, it looks good to me.
Reviewed-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
^ permalink raw reply
* Re: [PATCH v6 3/3] hyperv: Cleanly shutdown root partition with MSHV
From: Nuno Das Neves @ 2025-12-03 23:00 UTC (permalink / raw)
To: Praveen K Paladugu, kys, haiyangz, wei.liu, decui, tglx, mingo,
linux-hyperv, linux-kernel, bp, dave.hansen, x86, hpa, arnd
Cc: anbelski, easwar.hariharan, skinsburskii
In-Reply-To: <20251126215013.11549-4-prapal@linux.microsoft.com>
On 11/26/2025 1:49 PM, Praveen K Paladugu wrote:
> When a root partition running on MSHV is powered off, the default
> behavior is to write ACPI registers to power-off. However, this ACPI
> write is intercepted by MSHV and will result in a Machine Check
> Exception(MCE).
>
> The root partition eventually panics with a trace similar to:
>
> [ 81.306348] reboot: Power down
> [ 81.314709] mce: [Hardware Error]: CPU 0: Machine Check Exception: 4 Bank 0: b2000000c0060001
> [ 81.314711] mce: [Hardware Error]: TSC 3b8cb60a66 PPIN 11d98332458e4ea9
> [ 81.314713] mce: [Hardware Error]: PROCESSOR 0:606a6 TIME 1759339405 SOCKET 0 APIC 0 microcode ffffffff
> [ 81.314715] mce: [Hardware Error]: Run the above through 'mcelog --ascii'
> [ 81.314716] mce: [Hardware Error]: Machine check: Processor context corrupt
> [ 81.314717] Kernel panic - not syncing: Fatal machine check
>
> To correctly shutdown a root partition running on MSHV hypervisor, sleep
> state information must be configured within the hypervsior. Later, the
> HVCALL_ENTER_SLEEP_STATE hypercall should be invoked as the last step in
> the shutdown sequence.
>
> The previous patch configures the sleep state information and this patch
> invokes HVCALL_ENTER_SLEEP_STATE hypercall to cleanly shutdown the root
> partition.
>
Avoid statements like "The previous patch", since these patches may not
always follow each other directly once pulled into other trees, ported, etc.
You could explicitly mention the dependency on calling
hv_sleep_notifiers_register() before HVCALL_ENTER_SLEEP_STATE will work
correctly.
> Signed-off-by: Praveen K Paladugu <prapal@linux.microsoft.com>
> Co-developed-by: Anatol Belski <anbelski@linux.microsoft.com>
> Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
> Reviewed-by: Easwar Hariharan <easwar.hariharan@linux.microsoft.com>
> ---
> arch/x86/include/asm/mshyperv.h | 2 ++
> arch/x86/kernel/cpu/mshyperv.c | 2 ++
> drivers/hv/mshv_common.c | 18 ++++++++++++++++++
> 3 files changed, 22 insertions(+)
>
> diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h
> index 166053df0484..4c22f3257368 100644
> --- a/arch/x86/include/asm/mshyperv.h
> +++ b/arch/x86/include/asm/mshyperv.h
> @@ -183,9 +183,11 @@ void hv_apic_init(void);
> void __init hv_init_spinlocks(void);
> bool hv_vcpu_is_preempted(int vcpu);
> void hv_sleep_notifiers_register(void);
> +void hv_machine_power_off(void);
> #else
> static inline void hv_apic_init(void) {}
> static inline void hv_sleep_notifiers_register(void) {};
> +static inline void hv_machine_power_off(void) {};
> #endif
>
> struct irq_domain *hv_create_pci_msi_domain(void);
> diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
> index fac9953a72ef..579fb2c64cfd 100644
> --- a/arch/x86/kernel/cpu/mshyperv.c
> +++ b/arch/x86/kernel/cpu/mshyperv.c
> @@ -621,6 +621,8 @@ static void __init ms_hyperv_init_platform(void)
> #endif
>
> #if IS_ENABLED(CONFIG_HYPERV)
> + if (hv_root_partition())
> + machine_ops.power_off = hv_machine_power_off;
> #if defined(CONFIG_KEXEC_CORE)
> machine_ops.shutdown = hv_machine_shutdown;
> #endif
> diff --git a/drivers/hv/mshv_common.c b/drivers/hv/mshv_common.c
> index f1d4e81107ee..28905e3ed9c0 100644
> --- a/drivers/hv/mshv_common.c
> +++ b/drivers/hv/mshv_common.c
> @@ -217,4 +217,22 @@ void hv_sleep_notifiers_register(void)
> pr_err("%s: cannot register reboot notifier %d\n", __func__,
> ret);
> }
> +
> +/*
> + * Power off the machine by entering S5 sleep state via Hyper-V hypercall.
> + * This call does not return if successful.
> + */
> +void hv_machine_power_off(void)
> +{
> + unsigned long flags;
> + struct hv_input_enter_sleep_state *in;
> +
> + local_irq_save(flags);
> + in = *this_cpu_ptr(hyperv_pcpu_input_arg);
> + in->sleep_state = HV_SLEEP_STATE_S5;
> +
> + (void)hv_do_hypercall(HVCALL_ENTER_SLEEP_STATE, in, NULL);
> + local_irq_restore(flags);
We don't expect to return here. If we do for some reason, it is surely
a bug.
I suggest either:
- fall back to native_machine_power_off(), or
- BUG(), since the machine shouldn't continue running
> +
> +}
> #endif
^ permalink raw reply
* Re: [PATCH v9 4/6] Drivers: hv: Fix huge page handling in memory region traversal
From: Nuno Das Neves @ 2025-12-03 22:01 UTC (permalink / raw)
To: Stanislav Kinsburskii, kys, haiyangz, wei.liu, decui
Cc: linux-hyperv, linux-kernel
In-Reply-To: <176479805682.304819.16453923977474855999.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>
On 12/3/2025 1:40 PM, Stanislav Kinsburskii wrote:
> The previous code assumed that if a region's first page was huge, the
> entire region consisted of huge pages and stored this in a large_pages
> flag. This premise is incorrect not only for movable regions (where
> pages can be split and merged on invalidate callbacks or page faults),
> but even for pinned regions: THPs can be split and merged during
> allocation, so a large, pinned region may contain a mix of huge and
> regular pages.
>
> This change removes the large_pages flag and replaces region-wide
> assumptions with per-chunk inspection of the actual page size when
> mapping, unmapping, sharing, and unsharing. This makes huge page
> handling correct for mixed-page regions and avoids relying on stale
> metadata that can easily become invalid as memory is remapped.
>
> Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
> Reviewed-by: Anirudh Rayabharam (Microsoft) <anirudh@anirudhrb.com>
> ---
> drivers/hv/mshv_regions.c | 219 +++++++++++++++++++++++++++++++++++++++------
> drivers/hv/mshv_root.h | 3 -
> 2 files changed, 191 insertions(+), 31 deletions(-)
>
Reviewed-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
^ permalink raw reply
* Re: [PATCH 2/3] mshv: Add definitions for stats pages
From: Nuno Das Neves @ 2025-12-03 21:58 UTC (permalink / raw)
To: Stanislav Kinsburskii
Cc: linux-hyperv, linux-kernel, kys, haiyangz, wei.liu, decui, longli,
mhklinux, prapal, mrathor, paekkaladevi
In-Reply-To: <aTCKL1XBxZ8w6kqY@skinsburskii.localdomain>
On 12/3/2025 11:06 AM, Stanislav Kinsburskii wrote:
> On Wed, Dec 03, 2025 at 09:53:24AM -0800, Nuno Das Neves wrote:
>> Add the definitions for hypervisor, logical processor, and partition
>> stats pages.
>>
>> Move the definition for the VP stats page to its rightful place in
>> hvhdk.h, and add the missing members.
>>
>> These enum members retain their CamelCase style, since they are imported
>> directly from the hypervisor code They will be stringified when printing
>> the stats out, and retain more readability in this form.
>>
>> Signed-off-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
>> ---
>> drivers/hv/mshv_root_main.c | 17 --
>> include/hyperv/hvhdk.h | 437 ++++++++++++++++++++++++++++++++++++
>> 2 files changed, 437 insertions(+), 17 deletions(-)
>>
>
> <snip>
>
>> +
>> +enum hv_stats_partition_counters { /* HV_PROCESS_COUNTER */
>> + PartitionVirtualProcessors = 1,
>> + PartitionTlbSize = 3,
>> + PartitionAddressSpaces = 4,
>> + PartitionDepositedPages = 5,
>> + PartitionGpaPages = 6,
>> + PartitionGpaSpaceModifications = 7,
>> + PartitionVirtualTlbFlushEntires = 8,
>> + PartitionRecommendedTlbSize = 9,
>> + PartitionGpaPages4K = 10,
>> + PartitionGpaPages2M = 11,
>> + PartitionGpaPages1G = 12,
>> + PartitionGpaPages512G = 13,
>> + PartitionDevicePages4K = 14,
>> + PartitionDevicePages2M = 15,
>> + PartitionDevicePages1G = 16,
>> + PartitionDevicePages512G = 17,
>> + PartitionAttachedDevices = 18,
>> + PartitionDeviceInterruptMappings = 19,
>> + PartitionIoTlbFlushes = 20,
>> + PartitionIoTlbFlushCost = 21,
>> + PartitionDeviceInterruptErrors = 22,
>> + PartitionDeviceDmaErrors = 23,
>> + PartitionDeviceInterruptThrottleEvents = 24,
>> + PartitionSkippedTimerTicks = 25,
>> + PartitionPartitionId = 26,
>> +#if IS_ENABLED(CONFIG_X86)
>
> Why isn't this CONFIG_X86_64 instead (here and below)?
An oversight, thanks for catching it.
I'll change it to CONFIG_X86_64
>
> Thanks,
> Stanislav
^ permalink raw reply
* Re: [PATCH 1/3] mshv: Ignore second stats page map result failure
From: Nuno Das Neves @ 2025-12-03 21:43 UTC (permalink / raw)
To: Stanislav Kinsburskii
Cc: linux-hyperv, linux-kernel, kys, haiyangz, wei.liu, decui, longli,
mhklinux, prapal, mrathor, paekkaladevi
In-Reply-To: <aTCJgA3p9UKcIiU5@skinsburskii.localdomain>
On 12/3/2025 11:03 AM, Stanislav Kinsburskii wrote:
> On Wed, Dec 03, 2025 at 09:53:23AM -0800, Nuno Das Neves wrote:
>> From: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
>>
>> Older versions of the hypervisor do not support HV_STATS_AREA_PARENT
>> and return HV_STATUS_INVALID_PARAMETER for the second stats page
>> mapping request.
>>
>> This results a failure in module init. Instead of failing, gracefully
>> fall back to populating stats_pages[HV_STATS_AREA_PARENT] with the
>> already-mapped stats_pages[HV_STATS_AREA_SELF].
>>
>> Signed-off-by: Purna Pavan Chandra Aekkaladevi <paekkaladevi@linux.microsoft.com>
>> Signed-off-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
>> ---
>> drivers/hv/mshv_root_hv_call.c | 43 ++++++++++++++++++++++++++++++----
>> drivers/hv/mshv_root_main.c | 3 +++
>> 2 files changed, 42 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c
>> index 598eaff4ff29..0427785bb7fe 100644
>> --- a/drivers/hv/mshv_root_hv_call.c
>> +++ b/drivers/hv/mshv_root_hv_call.c
>> @@ -855,6 +855,24 @@ static int hv_call_map_stats_page2(enum hv_stats_object_type type,
>> return ret;
>> }
>>
>> +static int
>> +hv_stats_get_area_type(enum hv_stats_object_type type,
>> + const union hv_stats_object_identity *identity)
>> +{
>> + switch (type) {
>> + case HV_STATS_OBJECT_HYPERVISOR:
>> + return identity->hv.stats_area_type;
>> + case HV_STATS_OBJECT_LOGICAL_PROCESSOR:
>> + return identity->lp.stats_area_type;
>> + case HV_STATS_OBJECT_PARTITION:
>> + return identity->partition.stats_area_type;
>> + case HV_STATS_OBJECT_VP:
>> + return identity->vp.stats_area_type;
>> + }
>> +
>> + return -EINVAL;
>> +}
>> +
>> static int hv_call_map_stats_page(enum hv_stats_object_type type,
>> const union hv_stats_object_identity *identity,
>> void **addr)
>> @@ -863,7 +881,7 @@ static int hv_call_map_stats_page(enum hv_stats_object_type type,
>> struct hv_input_map_stats_page *input;
>> struct hv_output_map_stats_page *output;
>> u64 status, pfn;
>> - int ret = 0;
>> + int hv_status, ret = 0;
>>
>> do {
>> local_irq_save(flags);
>> @@ -878,11 +896,28 @@ static int hv_call_map_stats_page(enum hv_stats_object_type type,
>> pfn = output->map_location;
>>
>> local_irq_restore(flags);
>> - if (hv_result(status) != HV_STATUS_INSUFFICIENT_MEMORY) {
>> - ret = hv_result_to_errno(status);
>> +
>> + hv_status = hv_result(status);
>> + if (hv_status != HV_STATUS_INSUFFICIENT_MEMORY) {
>> if (hv_result_success(status))
>> break;
>> - return ret;
>> +
>> + /*
>> + * Older versions of the hypervisor do not support the
>> + * PARENT stats area. In this case return "success" but
>> + * set the page to NULL. The caller should check for
>> + * this case and instead just use the SELF area.
>> + */
>> + if (hv_stats_get_area_type(type, identity) == HV_STATS_AREA_PARENT &&
>> + hv_status == HV_STATUS_INVALID_PARAMETER) {
>> + pr_debug_once("%s: PARENT area type is unsupported\n",
>> + __func__);
>
> Nit: this gebug once looks a bit odd. Why not having it printed always
> (especially given the unconfidional status print a few lines below)?
>
This isn't indicative of an error, but a missing feature in an older
hypervisor. The rationale of _once is that this information only needs
to be printed once.
Now that I think about it however, it doesn't really change how the
statistics would be interpreted. In the situation where this condition
is triggered (root partition on old hypervisor), all the stats info
is in the HV_STATS_AREA_SELF page anyway. So, maybe this can just be
removed.
> Reviewed-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
>
> Thanks,
> Stanislav
>
>> + *addr = NULL;
>> + return 0;
>> + }
>> +
>> + hv_status_debug(status, "\n");
>> + return hv_result_to_errno(status);
>> }
>>
>> ret = hv_call_deposit_pages(NUMA_NO_NODE,
>> diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
>> index bc15d6f6922f..f59a4ab47685 100644
>> --- a/drivers/hv/mshv_root_main.c
>> +++ b/drivers/hv/mshv_root_main.c
>> @@ -905,6 +905,9 @@ static int mshv_vp_stats_map(u64 partition_id, u32 vp_index,
>> if (err)
>> goto unmap_self;
>>
>> + if (!stats_pages[HV_STATS_AREA_PARENT])
>> + stats_pages[HV_STATS_AREA_PARENT] = stats_pages[HV_STATS_AREA_SELF];
>> +
>> return 0;
>>
>> unmap_self:
>> --
>> 2.34.1
>>
^ permalink raw reply
* [PATCH v9 6/6] Drivers: hv: Add support for movable memory regions
From: Stanislav Kinsburskii @ 2025-12-03 21:41 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui; +Cc: linux-hyperv, linux-kernel
In-Reply-To: <176479772384.304819.9168337792948347657.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>
Introduce support for movable memory regions in the Hyper-V root partition
driver to improve memory management flexibility and enable advanced use
cases such as dynamic memory remapping.
Mirror the address space between the Linux root partition and guest VMs
using HMM. The root partition owns the memory, while guest VMs act as
devices with page tables managed via hypercalls. MSHV handles VP intercepts
by invoking hmm_range_fault() and updating SLAT entries. When memory is
reclaimed, HMM invalidates the relevant regions, prompting MSHV to clear
SLAT entries; guest VMs will fault again on access.
Integrate mmu_interval_notifier for movable regions, implement handlers for
HMM faults and memory invalidation, and update memory region mapping logic
to support movable regions.
While MMU notifiers are commonly used in virtualization drivers, this
implementation leverages HMM (Heterogeneous Memory Management) for its
specialized functionality. HMM provides a framework for mirroring,
invalidation, and fault handling, reducing boilerplate and improving
maintainability compared to generic MMU notifiers.
Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Reviewed-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
---
drivers/hv/Kconfig | 2
drivers/hv/mshv_regions.c | 218 ++++++++++++++++++++++++++++++++++++++++++-
drivers/hv/mshv_root.h | 20 +++-
drivers/hv/mshv_root_main.c | 142 +++++++++++++++++++++++-----
4 files changed, 346 insertions(+), 36 deletions(-)
diff --git a/drivers/hv/Kconfig b/drivers/hv/Kconfig
index d4a8d349200c..7937ac0cbd0f 100644
--- a/drivers/hv/Kconfig
+++ b/drivers/hv/Kconfig
@@ -76,6 +76,8 @@ config MSHV_ROOT
depends on PAGE_SIZE_4KB
select EVENTFD
select VIRT_XFER_TO_GUEST_WORK
+ select HMM_MIRROR
+ select MMU_NOTIFIER
default n
help
Select this option to enable support for booting and running as root
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 4ec78fdaf56d..202b9d551e39 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -7,6 +7,8 @@
* Authors: Microsoft Linux virtualization team
*/
+#include <linux/hmm.h>
+#include <linux/hyperv.h>
#include <linux/kref.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
@@ -15,6 +17,8 @@
#include "mshv_root.h"
+#define MSHV_MAP_FAULT_IN_PAGES PTRS_PER_PMD
+
/**
* mshv_region_process_chunk - Processes a contiguous chunk of memory pages
* in a region.
@@ -134,8 +138,7 @@ static int mshv_region_process_range(struct mshv_mem_region *region,
}
struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
- u64 uaddr, u32 flags,
- bool is_mmio)
+ u64 uaddr, u32 flags)
{
struct mshv_mem_region *region;
@@ -152,9 +155,6 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
if (flags & BIT(MSHV_SET_MEM_BIT_EXECUTABLE))
region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
- if (!is_mmio)
- region->flags.range_pinned = true;
-
kref_init(®ion->refcount);
return region;
@@ -245,7 +245,7 @@ int mshv_region_map(struct mshv_mem_region *region)
static void mshv_region_invalidate_pages(struct mshv_mem_region *region,
u64 page_offset, u64 page_count)
{
- if (region->flags.range_pinned)
+ if (region->type == MSHV_REGION_TYPE_MEM_PINNED)
unpin_user_pages(region->pages + page_offset, page_count);
memset(region->pages + page_offset, 0,
@@ -321,6 +321,9 @@ static void mshv_region_destroy(struct kref *ref)
struct mshv_partition *partition = region->partition;
int ret;
+ if (region->type == MSHV_REGION_TYPE_MEM_MOVABLE)
+ mshv_region_movable_fini(region);
+
if (mshv_partition_encrypted(partition)) {
ret = mshv_region_share(region);
if (ret) {
@@ -347,3 +350,206 @@ int mshv_region_get(struct mshv_mem_region *region)
{
return kref_get_unless_zero(®ion->refcount);
}
+
+/**
+ * mshv_region_hmm_fault_and_lock - Handle HMM faults and lock the memory region
+ * @region: Pointer to the memory region structure
+ * @range: Pointer to the HMM range structure
+ *
+ * This function performs the following steps:
+ * 1. Reads the notifier sequence for the HMM range.
+ * 2. Acquires a read lock on the memory map.
+ * 3. Handles HMM faults for the specified range.
+ * 4. Releases the read lock on the memory map.
+ * 5. If successful, locks the memory region mutex.
+ * 6. Verifies if the notifier sequence has changed during the operation.
+ * If it has, releases the mutex and returns -EBUSY to match with
+ * hmm_range_fault() return code for repeating.
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+static int mshv_region_hmm_fault_and_lock(struct mshv_mem_region *region,
+ struct hmm_range *range)
+{
+ int ret;
+
+ range->notifier_seq = mmu_interval_read_begin(range->notifier);
+ mmap_read_lock(region->mni.mm);
+ ret = hmm_range_fault(range);
+ mmap_read_unlock(region->mni.mm);
+ if (ret)
+ return ret;
+
+ mutex_lock(®ion->mutex);
+
+ if (mmu_interval_read_retry(range->notifier, range->notifier_seq)) {
+ mutex_unlock(®ion->mutex);
+ cond_resched();
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/**
+ * mshv_region_range_fault - Handle memory range faults for a given region.
+ * @region: Pointer to the memory region structure.
+ * @page_offset: Offset of the page within the region.
+ * @page_count: Number of pages to handle.
+ *
+ * This function resolves memory faults for a specified range of pages
+ * within a memory region. It uses HMM (Heterogeneous Memory Management)
+ * to fault in the required pages and updates the region's page array.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int mshv_region_range_fault(struct mshv_mem_region *region,
+ u64 page_offset, u64 page_count)
+{
+ struct hmm_range range = {
+ .notifier = ®ion->mni,
+ .default_flags = HMM_PFN_REQ_FAULT | HMM_PFN_REQ_WRITE,
+ };
+ unsigned long *pfns;
+ int ret;
+ u64 i;
+
+ pfns = kmalloc_array(page_count, sizeof(*pfns), GFP_KERNEL);
+ if (!pfns)
+ return -ENOMEM;
+
+ range.hmm_pfns = pfns;
+ range.start = region->start_uaddr + page_offset * HV_HYP_PAGE_SIZE;
+ range.end = range.start + page_count * HV_HYP_PAGE_SIZE;
+
+ do {
+ ret = mshv_region_hmm_fault_and_lock(region, &range);
+ } while (ret == -EBUSY);
+
+ if (ret)
+ goto out;
+
+ for (i = 0; i < page_count; i++)
+ region->pages[page_offset + i] = hmm_pfn_to_page(pfns[i]);
+
+ ret = mshv_region_remap_pages(region, region->hv_map_flags,
+ page_offset, page_count);
+
+ mutex_unlock(®ion->mutex);
+out:
+ kfree(pfns);
+ return ret;
+}
+
+bool mshv_region_handle_gfn_fault(struct mshv_mem_region *region, u64 gfn)
+{
+ u64 page_offset, page_count;
+ int ret;
+
+ /* Align the page offset to the nearest MSHV_MAP_FAULT_IN_PAGES. */
+ page_offset = ALIGN_DOWN(gfn - region->start_gfn,
+ MSHV_MAP_FAULT_IN_PAGES);
+
+ /* Map more pages than requested to reduce the number of faults. */
+ page_count = min(region->nr_pages - page_offset,
+ MSHV_MAP_FAULT_IN_PAGES);
+
+ ret = mshv_region_range_fault(region, page_offset, page_count);
+
+ WARN_ONCE(ret,
+ "p%llu: GPA intercept failed: region %#llx-%#llx, gfn %#llx, page_offset %llu, page_count %llu\n",
+ region->partition->pt_id, region->start_uaddr,
+ region->start_uaddr + (region->nr_pages << HV_HYP_PAGE_SHIFT),
+ gfn, page_offset, page_count);
+
+ return !ret;
+}
+
+/**
+ * mshv_region_interval_invalidate - Invalidate a range of memory region
+ * @mni: Pointer to the mmu_interval_notifier structure
+ * @range: Pointer to the mmu_notifier_range structure
+ * @cur_seq: Current sequence number for the interval notifier
+ *
+ * This function invalidates a memory region by remapping its pages with
+ * no access permissions. It locks the region's mutex to ensure thread safety
+ * and updates the sequence number for the interval notifier. If the range
+ * is blockable, it uses a blocking lock; otherwise, it attempts a non-blocking
+ * lock and returns false if unsuccessful.
+ *
+ * NOTE: Failure to invalidate a region is a serious error, as the pages will
+ * be considered freed while they are still mapped by the hypervisor.
+ * Any attempt to access such pages will likely crash the system.
+ *
+ * Return: true if the region was successfully invalidated, false otherwise.
+ */
+static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
+ const struct mmu_notifier_range *range,
+ unsigned long cur_seq)
+{
+ struct mshv_mem_region *region = container_of(mni,
+ struct mshv_mem_region,
+ mni);
+ u64 page_offset, page_count;
+ unsigned long mstart, mend;
+ int ret = -EPERM;
+
+ if (mmu_notifier_range_blockable(range))
+ mutex_lock(®ion->mutex);
+ else if (!mutex_trylock(®ion->mutex))
+ goto out_fail;
+
+ mmu_interval_set_seq(mni, cur_seq);
+
+ mstart = max(range->start, region->start_uaddr);
+ mend = min(range->end, region->start_uaddr +
+ (region->nr_pages << HV_HYP_PAGE_SHIFT));
+
+ page_offset = HVPFN_DOWN(mstart - region->start_uaddr);
+ page_count = HVPFN_DOWN(mend - mstart);
+
+ ret = mshv_region_remap_pages(region, HV_MAP_GPA_NO_ACCESS,
+ page_offset, page_count);
+ if (ret)
+ goto out_fail;
+
+ mshv_region_invalidate_pages(region, page_offset, page_count);
+
+ mutex_unlock(®ion->mutex);
+
+ return true;
+
+out_fail:
+ WARN_ONCE(ret,
+ "Failed to invalidate region %#llx-%#llx (range %#lx-%#lx, event: %u, pages %#llx-%#llx, mm: %#llx): %d\n",
+ region->start_uaddr,
+ region->start_uaddr + (region->nr_pages << HV_HYP_PAGE_SHIFT),
+ range->start, range->end, range->event,
+ page_offset, page_offset + page_count - 1, (u64)range->mm, ret);
+ return false;
+}
+
+static const struct mmu_interval_notifier_ops mshv_region_mni_ops = {
+ .invalidate = mshv_region_interval_invalidate,
+};
+
+void mshv_region_movable_fini(struct mshv_mem_region *region)
+{
+ mmu_interval_notifier_remove(®ion->mni);
+}
+
+bool mshv_region_movable_init(struct mshv_mem_region *region)
+{
+ int ret;
+
+ ret = mmu_interval_notifier_insert(®ion->mni, current->mm,
+ region->start_uaddr,
+ region->nr_pages << HV_HYP_PAGE_SHIFT,
+ &mshv_region_mni_ops);
+ if (ret)
+ return false;
+
+ mutex_init(®ion->mutex);
+
+ return true;
+}
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 4249534ba900..3c1d88b36741 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -15,6 +15,7 @@
#include <linux/hashtable.h>
#include <linux/dev_printk.h>
#include <linux/build_bug.h>
+#include <linux/mmu_notifier.h>
#include <uapi/linux/mshv.h>
/*
@@ -70,6 +71,12 @@ do { \
#define vp_info(v, fmt, ...) vp_devprintk(info, v, fmt, ##__VA_ARGS__)
#define vp_dbg(v, fmt, ...) vp_devprintk(dbg, v, fmt, ##__VA_ARGS__)
+enum mshv_region_type {
+ MSHV_REGION_TYPE_MEM_PINNED,
+ MSHV_REGION_TYPE_MEM_MOVABLE,
+ MSHV_REGION_TYPE_MMIO
+};
+
struct mshv_mem_region {
struct hlist_node hnode;
struct kref refcount;
@@ -77,11 +84,10 @@ struct mshv_mem_region {
u64 start_gfn;
u64 start_uaddr;
u32 hv_map_flags;
- struct {
- u64 range_pinned: 1;
- u64 reserved: 63;
- } flags;
struct mshv_partition *partition;
+ enum mshv_region_type type;
+ struct mmu_interval_notifier mni;
+ struct mutex mutex; /* protects region pages remapping */
struct page *pages[];
};
@@ -315,8 +321,7 @@ extern enum hv_scheduler_type hv_scheduler_type;
extern u8 * __percpu *hv_synic_eventring_tail;
struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
- u64 uaddr, u32 flags,
- bool is_mmio);
+ u64 uaddr, u32 flags);
int mshv_region_share(struct mshv_mem_region *region);
int mshv_region_unshare(struct mshv_mem_region *region);
int mshv_region_map(struct mshv_mem_region *region);
@@ -324,5 +329,8 @@ void mshv_region_invalidate(struct mshv_mem_region *region);
int mshv_region_pin(struct mshv_mem_region *region);
void mshv_region_put(struct mshv_mem_region *region);
int mshv_region_get(struct mshv_mem_region *region);
+bool mshv_region_handle_gfn_fault(struct mshv_mem_region *region, u64 gfn);
+void mshv_region_movable_fini(struct mshv_mem_region *region);
+bool mshv_region_movable_init(struct mshv_mem_region *region);
#endif /* _MSHV_ROOT_H_ */
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index aa1a11f4dc3e..9cf28a3f12fe 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -594,14 +594,98 @@ static long mshv_run_vp_with_root_scheduler(struct mshv_vp *vp)
static_assert(sizeof(struct hv_message) <= MSHV_RUN_VP_BUF_SZ,
"sizeof(struct hv_message) must not exceed MSHV_RUN_VP_BUF_SZ");
+static struct mshv_mem_region *
+mshv_partition_region_by_gfn(struct mshv_partition *partition, u64 gfn)
+{
+ struct mshv_mem_region *region;
+
+ hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
+ if (gfn >= region->start_gfn &&
+ gfn < region->start_gfn + region->nr_pages)
+ return region;
+ }
+
+ return NULL;
+}
+
+#ifdef CONFIG_X86_64
+static struct mshv_mem_region *
+mshv_partition_region_by_gfn_get(struct mshv_partition *p, u64 gfn)
+{
+ struct mshv_mem_region *region;
+
+ spin_lock(&p->pt_mem_regions_lock);
+ region = mshv_partition_region_by_gfn(p, gfn);
+ if (!region || !mshv_region_get(region)) {
+ spin_unlock(&p->pt_mem_regions_lock);
+ return NULL;
+ }
+ spin_unlock(&p->pt_mem_regions_lock);
+
+ return region;
+}
+
+/**
+ * mshv_handle_gpa_intercept - Handle GPA (Guest Physical Address) intercepts.
+ * @vp: Pointer to the virtual processor structure.
+ *
+ * This function processes GPA intercepts by identifying the memory region
+ * corresponding to the intercepted GPA, aligning the page offset, and
+ * mapping the required pages. It ensures that the region is valid and
+ * handles faults efficiently by mapping multiple pages at once.
+ *
+ * Return: true if the intercept was handled successfully, false otherwise.
+ */
+static bool mshv_handle_gpa_intercept(struct mshv_vp *vp)
+{
+ struct mshv_partition *p = vp->vp_partition;
+ struct mshv_mem_region *region;
+ struct hv_x64_memory_intercept_message *msg;
+ bool ret;
+ u64 gfn;
+
+ msg = (struct hv_x64_memory_intercept_message *)
+ vp->vp_intercept_msg_page->u.payload;
+
+ gfn = HVPFN_DOWN(msg->guest_physical_address);
+
+ region = mshv_partition_region_by_gfn_get(p, gfn);
+ if (!region)
+ return false;
+
+ /* Only movable memory ranges are supported for GPA intercepts */
+ if (region->type == MSHV_REGION_TYPE_MEM_MOVABLE)
+ ret = mshv_region_handle_gfn_fault(region, gfn);
+ else
+ ret = false;
+
+ mshv_region_put(region);
+
+ return ret;
+}
+#else /* CONFIG_X86_64 */
+static bool mshv_handle_gpa_intercept(struct mshv_vp *vp) { return false; }
+#endif /* CONFIG_X86_64 */
+
+static bool mshv_vp_handle_intercept(struct mshv_vp *vp)
+{
+ switch (vp->vp_intercept_msg_page->header.message_type) {
+ case HVMSG_GPA_INTERCEPT:
+ return mshv_handle_gpa_intercept(vp);
+ }
+ return false;
+}
+
static long mshv_vp_ioctl_run_vp(struct mshv_vp *vp, void __user *ret_msg)
{
long rc;
- if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
- rc = mshv_run_vp_with_root_scheduler(vp);
- else
- rc = mshv_run_vp_with_hyp_scheduler(vp);
+ do {
+ if (hv_scheduler_type == HV_SCHEDULER_TYPE_ROOT)
+ rc = mshv_run_vp_with_root_scheduler(vp);
+ else
+ rc = mshv_run_vp_with_hyp_scheduler(vp);
+ } while (rc == 0 && mshv_vp_handle_intercept(vp));
if (rc)
return rc;
@@ -1059,20 +1143,6 @@ static void mshv_async_hvcall_handler(void *data, u64 *status)
*status = partition->async_hypercall_status;
}
-static struct mshv_mem_region *
-mshv_partition_region_by_gfn(struct mshv_partition *partition, u64 gfn)
-{
- struct mshv_mem_region *region;
-
- hlist_for_each_entry(region, &partition->pt_mem_regions, hnode) {
- if (gfn >= region->start_gfn &&
- gfn < region->start_gfn + region->nr_pages)
- return region;
- }
-
- return NULL;
-}
-
/*
* NB: caller checks and makes sure mem->size is page aligned
* Returns: 0 with regionpp updated on success, or -errno
@@ -1097,11 +1167,18 @@ static int mshv_partition_create_region(struct mshv_partition *partition,
spin_unlock(&partition->pt_mem_regions_lock);
rg = mshv_region_create(mem->guest_pfn, nr_pages,
- mem->userspace_addr, mem->flags,
- is_mmio);
+ mem->userspace_addr, mem->flags);
if (IS_ERR(rg))
return PTR_ERR(rg);
+ if (is_mmio)
+ rg->type = MSHV_REGION_TYPE_MMIO;
+ else if (mshv_partition_encrypted(partition) ||
+ !mshv_region_movable_init(rg))
+ rg->type = MSHV_REGION_TYPE_MEM_PINNED;
+ else
+ rg->type = MSHV_REGION_TYPE_MEM_MOVABLE;
+
rg->partition = partition;
*regionpp = rg;
@@ -1217,11 +1294,28 @@ mshv_map_user_memory(struct mshv_partition *partition,
if (ret)
return ret;
- if (is_mmio)
- ret = hv_call_map_mmio_pages(partition->pt_id, mem.guest_pfn,
- mmio_pfn, HVPFN_DOWN(mem.size));
- else
+ switch (region->type) {
+ case MSHV_REGION_TYPE_MEM_PINNED:
ret = mshv_prepare_pinned_region(region);
+ break;
+ case MSHV_REGION_TYPE_MEM_MOVABLE:
+ /*
+ * For movable memory regions, remap with no access to let
+ * the hypervisor track dirty pages, enabling pre-copy live
+ * migration.
+ */
+ ret = hv_call_map_gpa_pages(partition->pt_id,
+ region->start_gfn,
+ region->nr_pages,
+ HV_MAP_GPA_NO_ACCESS, NULL);
+ break;
+ case MSHV_REGION_TYPE_MMIO:
+ ret = hv_call_map_mmio_pages(partition->pt_id,
+ region->start_gfn,
+ mmio_pfn,
+ region->nr_pages);
+ break;
+ }
if (ret)
goto errout;
^ permalink raw reply related
* [PATCH v9 5/6] Drivers: hv: Add refcount and locking to mem regions
From: Stanislav Kinsburskii @ 2025-12-03 21:41 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui; +Cc: linux-hyperv, linux-kernel
In-Reply-To: <176479772384.304819.9168337792948347657.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>
Introduce kref-based reference counting and spinlock protection for
memory regions in Hyper-V partition management. This change improves
memory region lifecycle management and ensures thread-safe access to the
region list.
Also improves the check for overlapped memory regions during region
creation, preventing duplicate or conflicting mappings.
Previously, the regions list was protected by the partition mutex.
However, this approach is too heavy for frequent fault and invalidation
operations. Finer grained locking is now used to improve efficiency and
concurrency.
This is a precursor to supporting movable memory regions. Fault and
invalidation handling for movable regions will require safe traversal of
the region list and holding a region reference while performing
invalidation or fault operations.
Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
---
drivers/hv/mshv_regions.c | 19 ++++++++++++++++---
drivers/hv/mshv_root.h | 6 +++++-
drivers/hv/mshv_root_main.c | 32 ++++++++++++++++++++++++--------
3 files changed, 45 insertions(+), 12 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 40126c12ab33..4ec78fdaf56d 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -7,6 +7,7 @@
* Authors: Microsoft Linux virtualization team
*/
+#include <linux/kref.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
@@ -154,6 +155,8 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
if (!is_mmio)
region->flags.range_pinned = true;
+ kref_init(®ion->refcount);
+
return region;
}
@@ -311,13 +314,13 @@ static int mshv_region_unmap(struct mshv_mem_region *region)
mshv_region_chunk_unmap);
}
-void mshv_region_destroy(struct mshv_mem_region *region)
+static void mshv_region_destroy(struct kref *ref)
{
+ struct mshv_mem_region *region =
+ container_of(ref, struct mshv_mem_region, refcount);
struct mshv_partition *partition = region->partition;
int ret;
- hlist_del(®ion->hnode);
-
if (mshv_partition_encrypted(partition)) {
ret = mshv_region_share(region);
if (ret) {
@@ -334,3 +337,13 @@ void mshv_region_destroy(struct mshv_mem_region *region)
vfree(region);
}
+
+void mshv_region_put(struct mshv_mem_region *region)
+{
+ kref_put(®ion->refcount, mshv_region_destroy);
+}
+
+int mshv_region_get(struct mshv_mem_region *region)
+{
+ return kref_get_unless_zero(®ion->refcount);
+}
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index ff3374f13691..4249534ba900 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -72,6 +72,7 @@ do { \
struct mshv_mem_region {
struct hlist_node hnode;
+ struct kref refcount;
u64 nr_pages;
u64 start_gfn;
u64 start_uaddr;
@@ -97,6 +98,8 @@ struct mshv_partition {
u64 pt_id;
refcount_t pt_ref_count;
struct mutex pt_mutex;
+
+ spinlock_t pt_mem_regions_lock;
struct hlist_head pt_mem_regions; // not ordered
u32 pt_vp_count;
@@ -319,6 +322,7 @@ int mshv_region_unshare(struct mshv_mem_region *region);
int mshv_region_map(struct mshv_mem_region *region);
void mshv_region_invalidate(struct mshv_mem_region *region);
int mshv_region_pin(struct mshv_mem_region *region);
-void mshv_region_destroy(struct mshv_mem_region *region);
+void mshv_region_put(struct mshv_mem_region *region);
+int mshv_region_get(struct mshv_mem_region *region);
#endif /* _MSHV_ROOT_H_ */
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index 5dfb933da981..aa1a11f4dc3e 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -1086,13 +1086,15 @@ static int mshv_partition_create_region(struct mshv_partition *partition,
u64 nr_pages = HVPFN_DOWN(mem->size);
/* Reject overlapping regions */
+ spin_lock(&partition->pt_mem_regions_lock);
hlist_for_each_entry(rg, &partition->pt_mem_regions, hnode) {
if (mem->guest_pfn + nr_pages <= rg->start_gfn ||
rg->start_gfn + rg->nr_pages <= mem->guest_pfn)
continue;
-
+ spin_unlock(&partition->pt_mem_regions_lock);
return -EEXIST;
}
+ spin_unlock(&partition->pt_mem_regions_lock);
rg = mshv_region_create(mem->guest_pfn, nr_pages,
mem->userspace_addr, mem->flags,
@@ -1224,8 +1226,9 @@ mshv_map_user_memory(struct mshv_partition *partition,
if (ret)
goto errout;
- /* Install the new region */
+ spin_lock(&partition->pt_mem_regions_lock);
hlist_add_head(®ion->hnode, &partition->pt_mem_regions);
+ spin_unlock(&partition->pt_mem_regions_lock);
return 0;
@@ -1244,17 +1247,27 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
if (!(mem.flags & BIT(MSHV_SET_MEM_BIT_UNMAP)))
return -EINVAL;
+ spin_lock(&partition->pt_mem_regions_lock);
+
region = mshv_partition_region_by_gfn(partition, mem.guest_pfn);
- if (!region)
- return -EINVAL;
+ if (!region) {
+ spin_unlock(&partition->pt_mem_regions_lock);
+ return -ENOENT;
+ }
/* Paranoia check */
if (region->start_uaddr != mem.userspace_addr ||
region->start_gfn != mem.guest_pfn ||
- region->nr_pages != HVPFN_DOWN(mem.size))
+ region->nr_pages != HVPFN_DOWN(mem.size)) {
+ spin_unlock(&partition->pt_mem_regions_lock);
return -EINVAL;
+ }
+
+ hlist_del(®ion->hnode);
- mshv_region_destroy(region);
+ spin_unlock(&partition->pt_mem_regions_lock);
+
+ mshv_region_put(region);
return 0;
}
@@ -1657,8 +1670,10 @@ static void destroy_partition(struct mshv_partition *partition)
remove_partition(partition);
hlist_for_each_entry_safe(region, n, &partition->pt_mem_regions,
- hnode)
- mshv_region_destroy(region);
+ hnode) {
+ hlist_del(®ion->hnode);
+ mshv_region_put(region);
+ }
/* Withdraw and free all pages we deposited */
hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, partition->pt_id);
@@ -1856,6 +1871,7 @@ mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev)
INIT_HLIST_HEAD(&partition->pt_devices);
+ spin_lock_init(&partition->pt_mem_regions_lock);
INIT_HLIST_HEAD(&partition->pt_mem_regions);
mshv_eventfd_init(partition);
^ permalink raw reply related
* [PATCH v9 4/6] Drivers: hv: Fix huge page handling in memory region traversal
From: Stanislav Kinsburskii @ 2025-12-03 21:40 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui; +Cc: linux-hyperv, linux-kernel
In-Reply-To: <176479772384.304819.9168337792948347657.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>
The previous code assumed that if a region's first page was huge, the
entire region consisted of huge pages and stored this in a large_pages
flag. This premise is incorrect not only for movable regions (where
pages can be split and merged on invalidate callbacks or page faults),
but even for pinned regions: THPs can be split and merged during
allocation, so a large, pinned region may contain a mix of huge and
regular pages.
This change removes the large_pages flag and replaces region-wide
assumptions with per-chunk inspection of the actual page size when
mapping, unmapping, sharing, and unsharing. This makes huge page
handling correct for mixed-page regions and avoids relying on stale
metadata that can easily become invalid as memory is remapped.
Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Reviewed-by: Anirudh Rayabharam (Microsoft) <anirudh@anirudhrb.com>
---
drivers/hv/mshv_regions.c | 219 +++++++++++++++++++++++++++++++++++++++------
drivers/hv/mshv_root.h | 3 -
2 files changed, 191 insertions(+), 31 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 35b866670840..40126c12ab33 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -14,6 +14,124 @@
#include "mshv_root.h"
+/**
+ * mshv_region_process_chunk - Processes a contiguous chunk of memory pages
+ * in a region.
+ * @region : Pointer to the memory region structure.
+ * @flags : Flags to pass to the handler.
+ * @page_offset: Offset into the region's pages array to start processing.
+ * @page_count : Number of pages to process.
+ * @handler : Callback function to handle the chunk.
+ *
+ * This function scans the region's pages starting from @page_offset,
+ * checking for contiguous present pages of the same size (normal or huge).
+ * It invokes @handler for the chunk of contiguous pages found. Returns the
+ * number of pages handled, or a negative error code if the first page is
+ * not present or the handler fails.
+ *
+ * Note: The @handler callback must be able to handle both normal and huge
+ * pages.
+ *
+ * Return: Number of pages handled, or negative error code.
+ */
+static long mshv_region_process_chunk(struct mshv_mem_region *region,
+ u32 flags,
+ u64 page_offset, u64 page_count,
+ int (*handler)(struct mshv_mem_region *region,
+ u32 flags,
+ u64 page_offset,
+ u64 page_count))
+{
+ u64 count, stride;
+ unsigned int page_order;
+ struct page *page;
+ int ret;
+
+ page = region->pages[page_offset];
+ if (!page)
+ return -EINVAL;
+
+ page_order = folio_order(page_folio(page));
+ /* The hypervisor only supports 4K and 2M page sizes */
+ if (page_order && page_order != HPAGE_PMD_ORDER)
+ return -EINVAL;
+
+ stride = 1 << page_order;
+
+ /* Start at stride since the first page is validated */
+ for (count = stride; count < page_count; count += stride) {
+ page = region->pages[page_offset + count];
+
+ /* Break if current page is not present */
+ if (!page)
+ break;
+
+ /* Break if page size changes */
+ if (page_order != folio_order(page_folio(page)))
+ break;
+ }
+
+ ret = handler(region, flags, page_offset, count);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+/**
+ * mshv_region_process_range - Processes a range of memory pages in a
+ * region.
+ * @region : Pointer to the memory region structure.
+ * @flags : Flags to pass to the handler.
+ * @page_offset: Offset into the region's pages array to start processing.
+ * @page_count : Number of pages to process.
+ * @handler : Callback function to handle each chunk of contiguous
+ * pages.
+ *
+ * Iterates over the specified range of pages in @region, skipping
+ * non-present pages. For each contiguous chunk of present pages, invokes
+ * @handler via mshv_region_process_chunk.
+ *
+ * Note: The @handler callback must be able to handle both normal and huge
+ * pages.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+static int mshv_region_process_range(struct mshv_mem_region *region,
+ u32 flags,
+ u64 page_offset, u64 page_count,
+ int (*handler)(struct mshv_mem_region *region,
+ u32 flags,
+ u64 page_offset,
+ u64 page_count))
+{
+ long ret;
+
+ if (page_offset + page_count > region->nr_pages)
+ return -EINVAL;
+
+ while (page_count) {
+ /* Skip non-present pages */
+ if (!region->pages[page_offset]) {
+ page_offset++;
+ page_count--;
+ continue;
+ }
+
+ ret = mshv_region_process_chunk(region, flags,
+ page_offset,
+ page_count,
+ handler);
+ if (ret < 0)
+ return ret;
+
+ page_offset += ret;
+ page_count -= ret;
+ }
+
+ return 0;
+}
+
struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
u64 uaddr, u32 flags,
bool is_mmio)
@@ -33,55 +151,86 @@ struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
if (flags & BIT(MSHV_SET_MEM_BIT_EXECUTABLE))
region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
- /* Note: large_pages flag populated when we pin the pages */
if (!is_mmio)
region->flags.range_pinned = true;
return region;
}
+static int mshv_region_chunk_share(struct mshv_mem_region *region,
+ u32 flags,
+ u64 page_offset, u64 page_count)
+{
+ struct page *page = region->pages[page_offset];
+
+ if (PageHuge(page) || PageTransCompound(page))
+ flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
+
+ return hv_call_modify_spa_host_access(region->partition->pt_id,
+ region->pages + page_offset,
+ page_count,
+ HV_MAP_GPA_READABLE |
+ HV_MAP_GPA_WRITABLE,
+ flags, true);
+}
+
int mshv_region_share(struct mshv_mem_region *region)
{
u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
- if (region->flags.large_pages)
+ return mshv_region_process_range(region, flags,
+ 0, region->nr_pages,
+ mshv_region_chunk_share);
+}
+
+static int mshv_region_chunk_unshare(struct mshv_mem_region *region,
+ u32 flags,
+ u64 page_offset, u64 page_count)
+{
+ struct page *page = region->pages[page_offset];
+
+ if (PageHuge(page) || PageTransCompound(page))
flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
return hv_call_modify_spa_host_access(region->partition->pt_id,
- region->pages, region->nr_pages,
- HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE,
- flags, true);
+ region->pages + page_offset,
+ page_count, 0,
+ flags, false);
}
int mshv_region_unshare(struct mshv_mem_region *region)
{
u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE;
- if (region->flags.large_pages)
- flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
-
- return hv_call_modify_spa_host_access(region->partition->pt_id,
- region->pages, region->nr_pages,
- 0,
- flags, false);
+ return mshv_region_process_range(region, flags,
+ 0, region->nr_pages,
+ mshv_region_chunk_unshare);
}
-static int mshv_region_remap_pages(struct mshv_mem_region *region,
- u32 map_flags,
+static int mshv_region_chunk_remap(struct mshv_mem_region *region,
+ u32 flags,
u64 page_offset, u64 page_count)
{
- if (page_offset + page_count > region->nr_pages)
- return -EINVAL;
+ struct page *page = region->pages[page_offset];
- if (region->flags.large_pages)
- map_flags |= HV_MAP_GPA_LARGE_PAGE;
+ if (PageHuge(page) || PageTransCompound(page))
+ flags |= HV_MAP_GPA_LARGE_PAGE;
return hv_call_map_gpa_pages(region->partition->pt_id,
region->start_gfn + page_offset,
- page_count, map_flags,
+ page_count, flags,
region->pages + page_offset);
}
+static int mshv_region_remap_pages(struct mshv_mem_region *region,
+ u32 map_flags,
+ u64 page_offset, u64 page_count)
+{
+ return mshv_region_process_range(region, map_flags,
+ page_offset, page_count,
+ mshv_region_chunk_remap);
+}
+
int mshv_region_map(struct mshv_mem_region *region)
{
u32 map_flags = region->hv_map_flags;
@@ -134,9 +283,6 @@ int mshv_region_pin(struct mshv_mem_region *region)
goto release_pages;
}
- if (PageHuge(region->pages[0]))
- region->flags.large_pages = true;
-
return 0;
release_pages:
@@ -144,10 +290,30 @@ int mshv_region_pin(struct mshv_mem_region *region)
return ret;
}
+static int mshv_region_chunk_unmap(struct mshv_mem_region *region,
+ u32 flags,
+ u64 page_offset, u64 page_count)
+{
+ struct page *page = region->pages[page_offset];
+
+ if (PageHuge(page) || PageTransCompound(page))
+ flags |= HV_UNMAP_GPA_LARGE_PAGE;
+
+ return hv_call_unmap_gpa_pages(region->partition->pt_id,
+ region->start_gfn + page_offset,
+ page_count, flags);
+}
+
+static int mshv_region_unmap(struct mshv_mem_region *region)
+{
+ return mshv_region_process_range(region, 0,
+ 0, region->nr_pages,
+ mshv_region_chunk_unmap);
+}
+
void mshv_region_destroy(struct mshv_mem_region *region)
{
struct mshv_partition *partition = region->partition;
- u32 unmap_flags = 0;
int ret;
hlist_del(®ion->hnode);
@@ -162,12 +328,7 @@ void mshv_region_destroy(struct mshv_mem_region *region)
}
}
- if (region->flags.large_pages)
- unmap_flags |= HV_UNMAP_GPA_LARGE_PAGE;
-
- /* ignore unmap failures and continue as process may be exiting */
- hv_call_unmap_gpa_pages(partition->pt_id, region->start_gfn,
- region->nr_pages, unmap_flags);
+ mshv_region_unmap(region);
mshv_region_invalidate(region);
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 0366f416c2f0..ff3374f13691 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -77,9 +77,8 @@ struct mshv_mem_region {
u64 start_uaddr;
u32 hv_map_flags;
struct {
- u64 large_pages: 1; /* 2MiB */
u64 range_pinned: 1;
- u64 reserved: 62;
+ u64 reserved: 63;
} flags;
struct mshv_partition *partition;
struct page *pages[];
^ permalink raw reply related
* [PATCH v9 3/6] Drivers: hv: Move region management to mshv_regions.c
From: Stanislav Kinsburskii @ 2025-12-03 21:40 UTC (permalink / raw)
To: kys, haiyangz, wei.liu, decui; +Cc: linux-hyperv, linux-kernel
In-Reply-To: <176479772384.304819.9168337792948347657.stgit@skinsburskii-cloud-desktop.internal.cloudapp.net>
Refactor memory region management functions from mshv_root_main.c into
mshv_regions.c for better modularity and code organization.
Adjust function calls and headers to use the new implementation. Improve
maintainability and separation of concerns in the mshv_root module.
Signed-off-by: Stanislav Kinsburskii <skinsburskii@linux.microsoft.com>
Reviewed-by: Anirudh Rayabharam (Microsoft) <anirudh@anirudhrb.com>
Reviewed-by: Nuno Das Neves <nunodasneves@linux.microsoft.com>
---
drivers/hv/Makefile | 2
drivers/hv/mshv_regions.c | 175 +++++++++++++++++++++++++++++++++++++++++++
drivers/hv/mshv_root.h | 10 ++
drivers/hv/mshv_root_main.c | 176 +++----------------------------------------
4 files changed, 198 insertions(+), 165 deletions(-)
create mode 100644 drivers/hv/mshv_regions.c
diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 58b8d07639f3..46d4f4f1b252 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -14,7 +14,7 @@ hv_vmbus-y := vmbus_drv.o \
hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o
hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o
mshv_root-y := mshv_root_main.o mshv_synic.o mshv_eventfd.o mshv_irq.o \
- mshv_root_hv_call.o mshv_portid_table.o
+ mshv_root_hv_call.o mshv_portid_table.o mshv_regions.o
mshv_vtl-y := mshv_vtl_main.o
# Code that must be built-in
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
new file mode 100644
index 000000000000..35b866670840
--- /dev/null
+++ b/drivers/hv/mshv_regions.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025, Microsoft Corporation.
+ *
+ * Memory region management for mshv_root module.
+ *
+ * Authors: Microsoft Linux virtualization team
+ */
+
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+
+#include <asm/mshyperv.h>
+
+#include "mshv_root.h"
+
+struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
+ u64 uaddr, u32 flags,
+ bool is_mmio)
+{
+ struct mshv_mem_region *region;
+
+ region = vzalloc(sizeof(*region) + sizeof(struct page *) * nr_pages);
+ if (!region)
+ return ERR_PTR(-ENOMEM);
+
+ region->nr_pages = nr_pages;
+ region->start_gfn = guest_pfn;
+ region->start_uaddr = uaddr;
+ region->hv_map_flags = HV_MAP_GPA_READABLE | HV_MAP_GPA_ADJUSTABLE;
+ if (flags & BIT(MSHV_SET_MEM_BIT_WRITABLE))
+ region->hv_map_flags |= HV_MAP_GPA_WRITABLE;
+ if (flags & BIT(MSHV_SET_MEM_BIT_EXECUTABLE))
+ region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
+
+ /* Note: large_pages flag populated when we pin the pages */
+ if (!is_mmio)
+ region->flags.range_pinned = true;
+
+ return region;
+}
+
+int mshv_region_share(struct mshv_mem_region *region)
+{
+ u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
+
+ if (region->flags.large_pages)
+ flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
+
+ return hv_call_modify_spa_host_access(region->partition->pt_id,
+ region->pages, region->nr_pages,
+ HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE,
+ flags, true);
+}
+
+int mshv_region_unshare(struct mshv_mem_region *region)
+{
+ u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE;
+
+ if (region->flags.large_pages)
+ flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
+
+ return hv_call_modify_spa_host_access(region->partition->pt_id,
+ region->pages, region->nr_pages,
+ 0,
+ flags, false);
+}
+
+static int mshv_region_remap_pages(struct mshv_mem_region *region,
+ u32 map_flags,
+ u64 page_offset, u64 page_count)
+{
+ if (page_offset + page_count > region->nr_pages)
+ return -EINVAL;
+
+ if (region->flags.large_pages)
+ map_flags |= HV_MAP_GPA_LARGE_PAGE;
+
+ return hv_call_map_gpa_pages(region->partition->pt_id,
+ region->start_gfn + page_offset,
+ page_count, map_flags,
+ region->pages + page_offset);
+}
+
+int mshv_region_map(struct mshv_mem_region *region)
+{
+ u32 map_flags = region->hv_map_flags;
+
+ return mshv_region_remap_pages(region, map_flags,
+ 0, region->nr_pages);
+}
+
+static void mshv_region_invalidate_pages(struct mshv_mem_region *region,
+ u64 page_offset, u64 page_count)
+{
+ if (region->flags.range_pinned)
+ unpin_user_pages(region->pages + page_offset, page_count);
+
+ memset(region->pages + page_offset, 0,
+ page_count * sizeof(struct page *));
+}
+
+void mshv_region_invalidate(struct mshv_mem_region *region)
+{
+ mshv_region_invalidate_pages(region, 0, region->nr_pages);
+}
+
+int mshv_region_pin(struct mshv_mem_region *region)
+{
+ u64 done_count, nr_pages;
+ struct page **pages;
+ __u64 userspace_addr;
+ int ret;
+
+ for (done_count = 0; done_count < region->nr_pages; done_count += ret) {
+ pages = region->pages + done_count;
+ userspace_addr = region->start_uaddr +
+ done_count * HV_HYP_PAGE_SIZE;
+ nr_pages = min(region->nr_pages - done_count,
+ MSHV_PIN_PAGES_BATCH_SIZE);
+
+ /*
+ * Pinning assuming 4k pages works for large pages too.
+ * All page structs within the large page are returned.
+ *
+ * Pin requests are batched because pin_user_pages_fast
+ * with the FOLL_LONGTERM flag does a large temporary
+ * allocation of contiguous memory.
+ */
+ ret = pin_user_pages_fast(userspace_addr, nr_pages,
+ FOLL_WRITE | FOLL_LONGTERM,
+ pages);
+ if (ret < 0)
+ goto release_pages;
+ }
+
+ if (PageHuge(region->pages[0]))
+ region->flags.large_pages = true;
+
+ return 0;
+
+release_pages:
+ mshv_region_invalidate_pages(region, 0, done_count);
+ return ret;
+}
+
+void mshv_region_destroy(struct mshv_mem_region *region)
+{
+ struct mshv_partition *partition = region->partition;
+ u32 unmap_flags = 0;
+ int ret;
+
+ hlist_del(®ion->hnode);
+
+ if (mshv_partition_encrypted(partition)) {
+ ret = mshv_region_share(region);
+ if (ret) {
+ pt_err(partition,
+ "Failed to regain access to memory, unpinning user pages will fail and crash the host error: %d\n",
+ ret);
+ return;
+ }
+ }
+
+ if (region->flags.large_pages)
+ unmap_flags |= HV_UNMAP_GPA_LARGE_PAGE;
+
+ /* ignore unmap failures and continue as process may be exiting */
+ hv_call_unmap_gpa_pages(partition->pt_id, region->start_gfn,
+ region->nr_pages, unmap_flags);
+
+ mshv_region_invalidate(region);
+
+ vfree(region);
+}
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 3eb815011b46..0366f416c2f0 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -312,4 +312,14 @@ extern struct mshv_root mshv_root;
extern enum hv_scheduler_type hv_scheduler_type;
extern u8 * __percpu *hv_synic_eventring_tail;
+struct mshv_mem_region *mshv_region_create(u64 guest_pfn, u64 nr_pages,
+ u64 uaddr, u32 flags,
+ bool is_mmio);
+int mshv_region_share(struct mshv_mem_region *region);
+int mshv_region_unshare(struct mshv_mem_region *region);
+int mshv_region_map(struct mshv_mem_region *region);
+void mshv_region_invalidate(struct mshv_mem_region *region);
+int mshv_region_pin(struct mshv_mem_region *region);
+void mshv_region_destroy(struct mshv_mem_region *region);
+
#endif /* _MSHV_ROOT_H_ */
diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c
index ec18984c3f2d..5dfb933da981 100644
--- a/drivers/hv/mshv_root_main.c
+++ b/drivers/hv/mshv_root_main.c
@@ -1059,117 +1059,6 @@ static void mshv_async_hvcall_handler(void *data, u64 *status)
*status = partition->async_hypercall_status;
}
-static int
-mshv_partition_region_share(struct mshv_mem_region *region)
-{
- u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
-
- if (region->flags.large_pages)
- flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
-
- return hv_call_modify_spa_host_access(region->partition->pt_id,
- region->pages, region->nr_pages,
- HV_MAP_GPA_READABLE | HV_MAP_GPA_WRITABLE,
- flags, true);
-}
-
-static int
-mshv_partition_region_unshare(struct mshv_mem_region *region)
-{
- u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE;
-
- if (region->flags.large_pages)
- flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE;
-
- return hv_call_modify_spa_host_access(region->partition->pt_id,
- region->pages, region->nr_pages,
- 0,
- flags, false);
-}
-
-static int
-mshv_region_remap_pages(struct mshv_mem_region *region, u32 map_flags,
- u64 page_offset, u64 page_count)
-{
- if (page_offset + page_count > region->nr_pages)
- return -EINVAL;
-
- if (region->flags.large_pages)
- map_flags |= HV_MAP_GPA_LARGE_PAGE;
-
- /* ask the hypervisor to map guest ram */
- return hv_call_map_gpa_pages(region->partition->pt_id,
- region->start_gfn + page_offset,
- page_count, map_flags,
- region->pages + page_offset);
-}
-
-static int
-mshv_region_map(struct mshv_mem_region *region)
-{
- u32 map_flags = region->hv_map_flags;
-
- return mshv_region_remap_pages(region, map_flags,
- 0, region->nr_pages);
-}
-
-static void
-mshv_region_invalidate_pages(struct mshv_mem_region *region,
- u64 page_offset, u64 page_count)
-{
- if (region->flags.range_pinned)
- unpin_user_pages(region->pages + page_offset, page_count);
-
- memset(region->pages + page_offset, 0,
- page_count * sizeof(struct page *));
-}
-
-static void
-mshv_region_invalidate(struct mshv_mem_region *region)
-{
- mshv_region_invalidate_pages(region, 0, region->nr_pages);
-}
-
-static int
-mshv_region_pin(struct mshv_mem_region *region)
-{
- u64 done_count, nr_pages;
- struct page **pages;
- __u64 userspace_addr;
- int ret;
-
- for (done_count = 0; done_count < region->nr_pages; done_count += ret) {
- pages = region->pages + done_count;
- userspace_addr = region->start_uaddr +
- done_count * HV_HYP_PAGE_SIZE;
- nr_pages = min(region->nr_pages - done_count,
- MSHV_PIN_PAGES_BATCH_SIZE);
-
- /*
- * Pinning assuming 4k pages works for large pages too.
- * All page structs within the large page are returned.
- *
- * Pin requests are batched because pin_user_pages_fast
- * with the FOLL_LONGTERM flag does a large temporary
- * allocation of contiguous memory.
- */
- ret = pin_user_pages_fast(userspace_addr, nr_pages,
- FOLL_WRITE | FOLL_LONGTERM,
- pages);
- if (ret < 0)
- goto release_pages;
- }
-
- if (PageHuge(region->pages[0]))
- region->flags.large_pages = true;
-
- return 0;
-
-release_pages:
- mshv_region_invalidate_pages(region, 0, done_count);
- return ret;
-}
-
static struct mshv_mem_region *
mshv_partition_region_by_gfn(struct mshv_partition *partition, u64 gfn)
{
@@ -1193,7 +1082,7 @@ static int mshv_partition_create_region(struct mshv_partition *partition,
struct mshv_mem_region **regionpp,
bool is_mmio)
{
- struct mshv_mem_region *region, *rg;
+ struct mshv_mem_region *rg;
u64 nr_pages = HVPFN_DOWN(mem->size);
/* Reject overlapping regions */
@@ -1205,26 +1094,15 @@ static int mshv_partition_create_region(struct mshv_partition *partition,
return -EEXIST;
}
- region = vzalloc(sizeof(*region) + sizeof(struct page *) * nr_pages);
- if (!region)
- return -ENOMEM;
-
- region->nr_pages = nr_pages;
- region->start_gfn = mem->guest_pfn;
- region->start_uaddr = mem->userspace_addr;
- region->hv_map_flags = HV_MAP_GPA_READABLE | HV_MAP_GPA_ADJUSTABLE;
- if (mem->flags & BIT(MSHV_SET_MEM_BIT_WRITABLE))
- region->hv_map_flags |= HV_MAP_GPA_WRITABLE;
- if (mem->flags & BIT(MSHV_SET_MEM_BIT_EXECUTABLE))
- region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
-
- /* Note: large_pages flag populated when we pin the pages */
- if (!is_mmio)
- region->flags.range_pinned = true;
+ rg = mshv_region_create(mem->guest_pfn, nr_pages,
+ mem->userspace_addr, mem->flags,
+ is_mmio);
+ if (IS_ERR(rg))
+ return PTR_ERR(rg);
- region->partition = partition;
+ rg->partition = partition;
- *regionpp = region;
+ *regionpp = rg;
return 0;
}
@@ -1262,7 +1140,7 @@ static int mshv_prepare_pinned_region(struct mshv_mem_region *region)
* access to guest memory regions.
*/
if (mshv_partition_encrypted(partition)) {
- ret = mshv_partition_region_unshare(region);
+ ret = mshv_region_unshare(region);
if (ret) {
pt_err(partition,
"Failed to unshare memory region (guest_pfn: %llu): %d\n",
@@ -1275,7 +1153,7 @@ static int mshv_prepare_pinned_region(struct mshv_mem_region *region)
if (ret && mshv_partition_encrypted(partition)) {
int shrc;
- shrc = mshv_partition_region_share(region);
+ shrc = mshv_region_share(region);
if (!shrc)
goto invalidate_region;
@@ -1356,36 +1234,6 @@ mshv_map_user_memory(struct mshv_partition *partition,
return ret;
}
-static void mshv_partition_destroy_region(struct mshv_mem_region *region)
-{
- struct mshv_partition *partition = region->partition;
- u32 unmap_flags = 0;
- int ret;
-
- hlist_del(®ion->hnode);
-
- if (mshv_partition_encrypted(partition)) {
- ret = mshv_partition_region_share(region);
- if (ret) {
- pt_err(partition,
- "Failed to regain access to memory, unpinning user pages will fail and crash the host error: %d\n",
- ret);
- return;
- }
- }
-
- if (region->flags.large_pages)
- unmap_flags |= HV_UNMAP_GPA_LARGE_PAGE;
-
- /* ignore unmap failures and continue as process may be exiting */
- hv_call_unmap_gpa_pages(partition->pt_id, region->start_gfn,
- region->nr_pages, unmap_flags);
-
- mshv_region_invalidate(region);
-
- vfree(region);
-}
-
/* Called for unmapping both the guest ram and the mmio space */
static long
mshv_unmap_user_memory(struct mshv_partition *partition,
@@ -1406,7 +1254,7 @@ mshv_unmap_user_memory(struct mshv_partition *partition,
region->nr_pages != HVPFN_DOWN(mem.size))
return -EINVAL;
- mshv_partition_destroy_region(region);
+ mshv_region_destroy(region);
return 0;
}
@@ -1810,7 +1658,7 @@ static void destroy_partition(struct mshv_partition *partition)
hlist_for_each_entry_safe(region, n, &partition->pt_mem_regions,
hnode)
- mshv_partition_destroy_region(region);
+ mshv_region_destroy(region);
/* Withdraw and free all pages we deposited */
hv_call_withdraw_memory(U64_MAX, NUMA_NO_NODE, partition->pt_id);
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox