From: Stanislav Kinsburskii <[email protected]> Sent: Wednesday, January 7, 2026 10:46 AM > > Ensure that a stride larger than 1 (huge page) is only used when page > points to a head of a huge page and both the guest frame number (gfn) and > the operation size (page_count) are aligned to the huge page size > (PTRS_PER_PMD). This matches the hypervisor requirement that map/unmap > operations for huge pages must be guest-aligned and cover a full huge page. > > Add mshv_chunk_stride() to encapsulate this alignment and page-order > validation, and plumb a huge_page flag into the region chunk handlers. > This prevents issuing large-page map/unmap/share operations that the > hypervisor would reject due to misaligned guest mappings. > > Fixes: abceb4297bf8 ("mshv: Fix huge page handling in memory region > traversal") > Signed-off-by: Stanislav Kinsburskii <[email protected]> > --- > drivers/hv/mshv_regions.c | 93 > ++++++++++++++++++++++++++++++--------------- > 1 file changed, 62 insertions(+), 31 deletions(-) > > diff --git a/drivers/hv/mshv_regions.c b/drivers/hv/mshv_regions.c > index 30bacba6aec3..adba3564d9f1 100644 > --- a/drivers/hv/mshv_regions.c > +++ b/drivers/hv/mshv_regions.c > @@ -19,6 +19,41 @@ > > #define MSHV_MAP_FAULT_IN_PAGES PTRS_PER_PMD > > +/** > + * mshv_chunk_stride - Compute stride for mapping guest memory > + * @page : The page to check for huge page backing > + * @gfn : Guest frame number for the mapping > + * @page_count: Total number of pages in the mapping > + * > + * Determines the appropriate stride (in pages) for mapping guest memory. > + * Uses huge page stride if the backing page is huge and the guest mapping > + * is properly aligned; otherwise falls back to single page stride. > + * > + * Return: Stride in pages, or -EINVAL if page order is unsupported. > + */ > +static int mshv_chunk_stride(struct page *page, > + u64 gfn, u64 page_count) > +{ > + unsigned int page_order; > + > + /* > + * Use single page stride by default. For huge page stride, the > + * page must be compound and point to the head of the compound > + * page, and both gfn and page_count must be huge-page aligned. > + */ > + if (!PageCompound(page) || !PageHead(page) || > + !IS_ALIGNED(gfn, PTRS_PER_PMD) || > + !IS_ALIGNED(page_count, PTRS_PER_PMD)) > + return 1; > + > + page_order = folio_order(page_folio(page)); > + /* The hypervisor only supports 2M huge page */ > + if (page_order != PMD_ORDER) > + return -EINVAL; > + > + return 1 << page_order; > +}
I think this works and solves the problem we've been discussing. My knowledge of PageCompound() and PageHead() is limited to the obvious, so I can't spot any weird edge cases that might occur. My preference would be to just check the alignment of the PFN corresponding to "page", which is what the hypervisor will do, but this approach provides a different kind of explicitness, and it's your call to make. With that, for the entire patch: Reviewed-by: Michael Kelley <[email protected]> > + > /** > * mshv_region_process_chunk - Processes a contiguous chunk of memory pages > * in a region. > @@ -45,25 +80,23 @@ static long mshv_region_process_chunk(struct > mshv_mem_region *region, > int (*handler)(struct mshv_mem_region > *region, > u32 flags, > u64 page_offset, > - u64 page_count)) > + u64 page_count, > + bool huge_page)) > { > - u64 count, stride; > - unsigned int page_order; > + u64 gfn = region->start_gfn + page_offset; > + u64 count; > struct page *page; > - int ret; > + int stride, 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 != PMD_ORDER) > - return -EINVAL; > + stride = mshv_chunk_stride(page, gfn, page_count); > + if (stride < 0) > + return stride; > > - stride = 1 << page_order; > - > - /* Start at stride since the first page is validated */ > + /* Start at stride since the first stride is validated */ > for (count = stride; count < page_count; count += stride) { > page = region->pages[page_offset + count]; > > @@ -71,12 +104,13 @@ static long mshv_region_process_chunk(struct > mshv_mem_region *region, > if (!page) > break; > > - /* Break if page size changes */ > - if (page_order != folio_order(page_folio(page))) > + /* Break if stride size changes */ > + if (stride != mshv_chunk_stride(page, gfn + count, > + page_count - count)) > break; > } > > - ret = handler(region, flags, page_offset, count); > + ret = handler(region, flags, page_offset, count, stride > 1); > if (ret) > return ret; > > @@ -108,7 +142,8 @@ static int mshv_region_process_range(struct > mshv_mem_region *region, > int (*handler)(struct mshv_mem_region > *region, > u32 flags, > u64 page_offset, > - u64 page_count)) > + u64 page_count, > + bool huge_page)) > { > long ret; > > @@ -162,11 +197,10 @@ struct mshv_mem_region *mshv_region_create(u64 > guest_pfn, u64 nr_pages, > > static int mshv_region_chunk_share(struct mshv_mem_region *region, > u32 flags, > - u64 page_offset, u64 page_count) > + u64 page_offset, u64 page_count, > + bool huge_page) > { > - struct page *page = region->pages[page_offset]; > - > - if (PageHuge(page) || PageTransCompound(page)) > + if (huge_page) > flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE; > > return hv_call_modify_spa_host_access(region->partition->pt_id, > @@ -188,11 +222,10 @@ int mshv_region_share(struct mshv_mem_region *region) > > static int mshv_region_chunk_unshare(struct mshv_mem_region *region, > u32 flags, > - u64 page_offset, u64 page_count) > + u64 page_offset, u64 page_count, > + bool huge_page) > { > - struct page *page = region->pages[page_offset]; > - > - if (PageHuge(page) || PageTransCompound(page)) > + if (huge_page) > flags |= HV_MODIFY_SPA_PAGE_HOST_ACCESS_LARGE_PAGE; > > return hv_call_modify_spa_host_access(region->partition->pt_id, > @@ -212,11 +245,10 @@ int mshv_region_unshare(struct mshv_mem_region *region) > > static int mshv_region_chunk_remap(struct mshv_mem_region *region, > u32 flags, > - u64 page_offset, u64 page_count) > + u64 page_offset, u64 page_count, > + bool huge_page) > { > - struct page *page = region->pages[page_offset]; > - > - if (PageHuge(page) || PageTransCompound(page)) > + if (huge_page) > flags |= HV_MAP_GPA_LARGE_PAGE; > > return hv_call_map_gpa_pages(region->partition->pt_id, > @@ -295,11 +327,10 @@ int mshv_region_pin(struct mshv_mem_region *region) > > static int mshv_region_chunk_unmap(struct mshv_mem_region *region, > u32 flags, > - u64 page_offset, u64 page_count) > + u64 page_offset, u64 page_count, > + bool huge_page) > { > - struct page *page = region->pages[page_offset]; > - > - if (PageHuge(page) || PageTransCompound(page)) > + if (huge_page) > flags |= HV_UNMAP_GPA_LARGE_PAGE; > > return hv_call_unmap_gpa_pages(region->partition->pt_id, > >
