[PATCH 7/7] mshv: Allocate pfns array only for pinned regions
From: Stanislav Kinsburskii
Date: Wed Apr 01 2026 - 18:22:39 EST
Convert pfns to a pointer allocated only for pinned regions that
actually need it for share/unshare/evict operations. Unpinned
regions use NULL since HMM handles their mappings dynamically.
The pfns array was previously a flexible array member, forcing
allocation for all regions regardless of memory type. This wastes
significant memory for unpinned HMM-managed regions which don't
need persistent PFN tracking - a 1GB region wastes 2MB for an
unused array.
This also allows using kzalloc for the main structure instead of
vzalloc, improving allocation efficiency and cache locality.
Simplify unpinned region invalidation by calling the hypervisor
directly rather than tracking PFNs. The tradeoff of skipping huge
page optimization is acceptable since invalidation ranges are
typically small and not performance-critical.
Add NULL checks where pfns array is required and update cleanup
to handle conditional allocation.
Signed-off-by: Stanislav Kinsburskii <skinsburskii@xxxxxxxxxxxxxxxxxxx>
---
drivers/hv/mshv_regions.c | 50 ++++++++++++++++++++++++++-------------------
drivers/hv/mshv_root.h | 6 ++++-
2 files changed, 33 insertions(+), 23 deletions(-)
diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c
index 3c2a35f3bc31..e3a6ade4919e 100644
--- a/drivers/hv/mshv_regions.c
+++ b/drivers/hv/mshv_regions.c
@@ -243,7 +243,7 @@ struct mshv_region *mshv_region_create(enum mshv_region_type type,
int ret = 0;
u64 i;
- region = vzalloc(sizeof(*region) + sizeof(unsigned long) * nr_pfns);
+ region = kzalloc_obj(*region);
if (!region)
return ERR_PTR(-ENOMEM);
@@ -255,6 +255,13 @@ struct mshv_region *mshv_region_create(enum mshv_region_type type,
&mshv_region_mni_ops);
break;
case MSHV_REGION_TYPE_MEM_PINNED:
+ region->mreg_pfns = vzalloc(sizeof(unsigned long) * nr_pfns);
+ if (!region->mreg_pfns) {
+ ret = -ENOMEM;
+ break;
+ }
+ for (i = 0; i < nr_pfns; i++)
+ region->mreg_pfns[i] = MSHV_INVALID_PFN;
break;
case MSHV_REGION_TYPE_MMIO:
region->mreg_mmio_pfn = mmio_pfn;
@@ -276,16 +283,13 @@ struct mshv_region *mshv_region_create(enum mshv_region_type type,
if (flags & BIT(MSHV_SET_MEM_BIT_EXECUTABLE))
region->hv_map_flags |= HV_MAP_GPA_EXECUTABLE;
- for (i = 0; i < nr_pfns; i++)
- region->mreg_pfns[i] = MSHV_INVALID_PFN;
-
mutex_init(®ion->mreg_mutex);
kref_init(®ion->mreg_refcount);
return region;
free_region:
- vfree(region);
+ kfree(region);
return ERR_PTR(ret);
}
@@ -312,6 +316,9 @@ static int mshv_region_share(struct mshv_region *region)
{
u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_SHARED;
+ if (!region->mreg_pfns)
+ return -EINVAL;
+
return mshv_region_process_range(region, flags,
0, region->nr_pfns,
region->mreg_pfns,
@@ -340,6 +347,9 @@ static int mshv_region_unshare(struct mshv_region *region)
{
u32 flags = HV_MODIFY_SPA_PAGE_HOST_ACCESS_MAKE_EXCLUSIVE;
+ if (!region->mreg_pfns)
+ return -EINVAL;
+
return mshv_region_process_range(region, flags,
0, region->nr_pfns,
region->mreg_pfns,
@@ -394,13 +404,11 @@ static void mshv_region_invalidate_pfns(struct mshv_region *region,
{
u64 i;
- for (i = pfn_offset; i < pfn_offset + pfn_count; i++) {
- if (!pfn_valid(region->mreg_pfns[i]))
- continue;
-
- if (region->mreg_type == MSHV_REGION_TYPE_MEM_PINNED)
- unpin_user_page(pfn_to_page(region->mreg_pfns[i]));
+ if (region->mreg_type != MSHV_REGION_TYPE_MEM_PINNED)
+ return;
+ for (i = pfn_offset; i < pfn_offset + pfn_count; i++) {
+ unpin_user_page(pfn_to_page(region->mreg_pfns[i]));
region->mreg_pfns[i] = MSHV_INVALID_PFN;
}
}
@@ -506,7 +514,8 @@ static void mshv_region_destroy(struct kref *ref)
mshv_region_invalidate(region);
- vfree(region);
+ vfree(region->mreg_pfns);
+ kfree(region);
}
void mshv_region_put(struct mshv_region *region)
@@ -616,10 +625,9 @@ static int mshv_region_hmm_fault_and_lock(struct mshv_region *region,
* leaving missing pages as invalid PFN markers.
* Used for initial region setup.
*
- * Collected PFNs are stored in region->mreg_pfns[] with HMM bookkeeping
- * flags cleared, then the range is mapped into the hypervisor. Present
- * PFNs get mapped with region access permissions; missing PFNs (zero
- * entries) get mapped with no-access permissions.
+ * HMM bookkeeping flags are stripped from collected PFNs before mapping.
+ * Present PFNs get mapped with region access permissions; missing PFNs
+ * (marked as MSHV_INVALID_PFN) get mapped with no-access permissions.
*
* Return: 0 on success, negative errno on failure.
*/
@@ -648,15 +656,17 @@ static int mshv_region_collect_and_map(struct mshv_region *region,
goto out;
for (i = 0; i < pfn_count; i++) {
- if (!(pfns[i] & HMM_PFN_VALID))
+ if (!(pfns[i] & HMM_PFN_VALID)) {
+ pfns[i] = MSHV_INVALID_PFN;
continue;
+ }
/* Drop HMM_PFN_* flags to ensure PFNs are valid. */
- region->mreg_pfns[pfn_offset + i] = pfns[i] & ~HMM_PFN_FLAGS;
+ pfns[i] &= ~HMM_PFN_FLAGS;
}
ret = mshv_region_remap_pfns(region, region->hv_map_flags,
pfn_offset, pfn_count,
- region->mreg_pfns + pfn_offset);
+ pfns);
mutex_unlock(®ion->mreg_mutex);
out:
@@ -770,8 +780,6 @@ static bool mshv_region_interval_invalidate(struct mmu_interval_notifier *mni,
if (ret)
goto out_unlock;
- mshv_region_invalidate_pfns(region, pfn_offset, pfn_count);
-
mutex_unlock(®ion->mreg_mutex);
return true;
diff --git a/drivers/hv/mshv_root.h b/drivers/hv/mshv_root.h
index 97659ba55418..e43bdbf1ada8 100644
--- a/drivers/hv/mshv_root.h
+++ b/drivers/hv/mshv_root.h
@@ -92,8 +92,10 @@ struct mshv_region {
enum mshv_region_type mreg_type;
struct mmu_interval_notifier mreg_mni;
struct mutex mreg_mutex; /* protects region PFNs remapping */
- u64 mreg_mmio_pfn;
- unsigned long mreg_pfns[];
+ union {
+ unsigned long *mreg_pfns;
+ u64 mreg_mmio_pfn;
+ };
};
struct mshv_irq_ack_notifier {