Skip to content

Drivers: hv: mshv_vtl: restore 2M VTL0 low mappings to bound PageTables - #159

Merged
Hardik Garg (hargar19) merged 1 commit into
product/hcl-main/6.18from
user/namjain/pagetablememfix
Aug 21, 2026
Merged

Drivers: hv: mshv_vtl: restore 2M VTL0 low mappings to bound PageTables#159
Hardik Garg (hargar19) merged 1 commit into
product/hcl-main/6.18from
user/namjain/pagetablememfix

Conversation

@namancse

Copy link
Copy Markdown
Contributor

Under sustained VTL0 I/O the paravisor's /proc/meminfo PageTables grew from ~660 kB into the tens of MB, steadily eating into VTL2 memory. The low VTL0 mapping (/dev/mshv_vtl_low) was installing a 4K PTE for every page it touched instead of a 2M PMD, so each touched 2M region cost a full extra page table.

The regression came from gating the huge-fault path on a registered struct page. Underhill registers lower-VTL memory with the kernel lazily - only for ranges handed to a device for DMA (expose_va) - because a small VTL2 cannot afford struct pages for all of guest RAM. The CPU relay path, however, touches far more memory than is ever DMA'd, so most huge faults hit not-yet-registered pfns, failed the gate, and fell back to 4K.

Fix it by mapping huge VTL0 faults by raw pfn again, via vmf_insert_pfn_{pmd,pud}() with no registration gate (the pre-v6.15 behaviour). vmf_insert_pfn_pmd() dereferences no struct page, so a 2M map is valid even for an unregistered pfn and PageTables stays flat.

Going back to the raw-pfn path re-exposes three issues that the recent folio-based rework had addressed; handle each without giving up 2M:

  • rmap/RSS drift: vmf_insert_folio_{pmd,pud}() add a file rmap and RSS that zap_huge_{pmd,pud}() never reverse on this VM_MIXEDMAP, non-DAX VMA (vma_is_special_huge() true, vma_is_dax() false), leaking a folio reference and tripping a "Bad rss-counter state" BUG. The pfn inserters carry no such state, so this drift simply goes away.

  • GUP refcount race: a huge pfn PMD holds no folio reference, so a zap racing pin_user_pages() could drop the refcount to 0 and warn in try_grab_folio(). Rather than a per-mapping reference, take a permanent reference on each pgmap folio in add_vtl0_mem(); VTL0 memory lives for the partition's lifetime, so the count never reaches 0.

  • GUP into a memmap-less range: GUP on a huge pfn PMD walks the struct page (follow_huge_pmd -> pmd_page -> try_grab_folio) and would oops for a range whose devm_memremap_pages() failed. Track such failed ranges and map them 4K (pte_special) so GUP fails gracefully with -EFAULT while all normal memory stays 2M.

Also make add_vtl0_mem() idempotent (an already-registered range returns success) so re-registration across servicing no longer reports a spurious failure.

Fixes: 775741a ("Drivers: hv: mshv_vtl: use folio-aware inserters for huge VTL0 mappings")

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a regression in /dev/mshv_vtl_low where sustained VTL0 I/O caused excessive page-table growth (4K PTEs per touched page) by restoring raw-PFN huge mappings (2M/1G) and adding safeguards around GUP behavior and memmap-less ranges.

Changes:

  • Restore huge VTL0 fault handling using vmf_insert_pfn_{pmd,pud}() (raw PFN) to avoid per-page PTE installation and rmap/RSS drift.
  • Track ranges where devm_memremap_pages() fails and force those ranges down a 4K pte_special fallback to prevent GUP from oopsing on memmap-less huge PMDs.
  • Make add_vtl0_mem() idempotent for already-registered ranges and pin pgmap folios to avoid GUP refcount warnings with raw-PFN huge mappings.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread drivers/hv/mshv_vtl_main.c Outdated
Comment thread drivers/hv/mshv_vtl_main.c
Comment thread drivers/hv/mshv_vtl_main.c
Copilot AI review requested due to automatic review settings August 17, 2026 04:55
@namancse
Naman Jain (namancse) force-pushed the user/namjain/pagetablememfix branch from 8050075 to 4126a31 Compare August 17, 2026 04:55
@namancse
Naman Jain (namancse) force-pushed the user/namjain/pagetablememfix branch from 4126a31 to ec6650a Compare August 17, 2026 05:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

drivers/hv/mshv_vtl_main.c:3991

  • This comment refers to unmap_mapping_range(), but add_vtl0_mem() uses unmap_mapping_pages(). Using the wrong function name here is misleading when reasoning about how the fallback PTEs are invalidated.
			 * The unmap_mapping_pages() in add_vtl0_mem() invalidates this

drivers/hv/mshv_vtl_main.c:1330

  • mshv_vtl_ioctl_add_vtl0_mem() is not serialized: the idempotency check is an RCU read without any writer-side exclusion. Two concurrent MSHV_ADD_VTL0_MEMORY ioctls for the same range can both pass mshv_vtl_low_range_registered(), then both call devm_memremap_pages(), folio_get() the same PFNs (permanent pin), and list_add_rcu() duplicate ranges. This can permanently inflate refcounts and grow mshv_vtl_low_ranges unexpectedly.

Please serialize add_vtl0_mem registrations (e.g., a dedicated mutex around the entire registration path, including the range_registered check, devm_memremap_pages(), folio_get() loop, and list_add_rcu()).

	/*
	 * Idempotent: a range already registered (e.g. re-registered across a
	 * servicing save/restore) keeps its existing mapping and pin; don't
	 * re-memremap or report a spurious failure.
	 */
	if (mshv_vtl_low_range_registered(vtl0_mem.start_pfn, vtl0_mem.last_pfn))
		return 0;

Copilot AI review requested due to automatic review settings August 17, 2026 05:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

drivers/hv/mshv_vtl_main.c:1300

  • mshv_vtl_low_failed_clear() only removes failed-range entries that are fully covered by the newly registered [start_pfn,last_pfn) range. If a prior devm_memremap_pages() failure recorded a larger range that partially overlaps this successful registration, the stale entry remains and mshv_vtl_low_pfn_failed() will keep forcing 4K fallback for PFNs that now do have a memmap.

Consider shrinking/splitting any overlapping failed-range entry to exclude the newly registered subrange (and only delete entries that are fully covered), so successful sub-registrations can resume 2M/1G mappings without losing tracking for the still-memmap-less portions.

	list_for_each_entry_safe(r, tmp, &mshv_vtl_low_failed_ranges, list) {
		if (r->start_pfn >= start_pfn && r->end_pfn <= last_pfn) {
			list_del_rcu(&r->list);
			kfree_rcu(r, rcu);
		}

drivers/hv/mshv_vtl_main.c:1330

  • The new "idempotent" early-return check is not race-safe: mshv_vtl_ioctl_add_vtl0_mem() is called without any higher-level mutex (see mshv_vtl_ioctl()), so two concurrent MSHV_ADD_VTL0_MEMORY calls for the same/overlapping range can both miss mshv_vtl_low_range_registered(). One thread may succeed while the other hits a transient failure (or -EBUSY) and incorrectly records the range as "failed" and/or unmaps existing mappings.

Consider serializing range registration attempts (e.g., a dedicated mutex around the add/failed_add/failed_clear + memremap_pages + list_add sequence), so idempotency and failed-range tracking remain correct under concurrent ioctls.

	if (mshv_vtl_low_range_registered(vtl0_mem.start_pfn, vtl0_mem.last_pfn))
		return 0;

@namancse

Copy link
Copy Markdown
Contributor Author

=== [1/1] Drivers: hv: mshv_vtl: restore 2M VTL0 low mappings to bound PageTables ===

  1. [HIGH] The PMD/PUD fault path can install huge raw-PFN mappings without proving that the whole huge span has valid struct pages, so slow GUP can dereference a missing vmemmap for never-registered PFNs or fo…
    Sources: review-prompts
    Evidence: The PMD/PUD fault path can install huge raw-PFN mappings without proving that
    the whole huge span has valid struct pages, so slow GUP can
    dereference a missing vmemmap for never-registered PFNs or for
    failed subranges that do not include the huge mapping's base PFN.
    Impact: Severity: High — matches "Deterministic kernel crash (oops/BUG/panic/WARN) on
    a code path that any normal kernel use can reach (driver bind,
    suspend/resume, network packet, filesystem mount)" because the VTL0
    mmap path is production driver functionality and GUP on the
    resulting mapping can reach pfn_to_page()/page_folio() on PFNs that
    the driver itself says have no struct pages. Proof:
    drivers/hv/mshv_vtl_main.c:4058-4063 only rejects non-shared VMAs
    and sets VM_HUGEPAGE|VM_MIXEDMAP; it does not require registration.
    drivers/hv/mshv_vtl_main.c:4002-4022 checks can_fault() and only
    mshv_vtl_low_pfn_failed(base_pfn) before vmf_insert_pfn_pmd();
    drivers/hv/mshv_vtl_main.c:4026-4032 does the same for PUD.
    drivers/hv/mshv_vtl_main.c:1239-1252 shows
    mshv_vtl_low_pfn_failed() is a single-PFN membership test, while
    drivers/hv/mshv_vtl_main.c:1372-1375 records the exact failed user
    range without expanding it to PMD/PUD boundaries. Therefore a
    never-submitted PFN, or a failed interval inside [base, base +
    HPAGE_*_NR) but not containing base, falls through to
    vmf_insert_pfn_pmd()/vmf_insert_pfn_pud(). mm/gup.c:713 and
    mm/gup.c:731 show slow PMD GUP calls pmd_page(pmdval) and
    try_grab_folio(page_folio(page)); mm/gup.c:668-674 shows slow PUD
    GUP does pfn_to_page(pfn) and try_grab_folio(page_folio(page)). The
    parent code gated PMD/PUD insertion on mshv_vtl_low_resolve_page()
    and exact folio_order(), so this is introduced by the patch.
    Suggested fix: (see Evidence)

: Why it doesn't fire in practice: GUP/[pin_user_pages] into VTL0 only happens for DMA, and OpenHCL calls add_vtl0_mem (registration) before exposing a range for DMA (expose_va). The CPU-relay path that touches unregistered memory uses memcpy, not GUP. So unregistered PFNs are never GUP'd. Registrations cannot be done for everything considering lazy memory registration design, and if in such cases, we fallback to smaller pages, it would also lead to problem of memory bloat.

  1. [HIGH] A racing huge fault can pass the failed-range check before a memremap failure is recorded, then install the memmap-less PMD/PUD after the failure path's one-time unmap has already run.
    Sources: review-prompts
    Evidence: A racing huge fault can pass the failed-range check before a memremap failure
    is recorded, then install the memmap-less PMD/PUD after the
    failure path's one-time unmap has already run.
    Impact: Severity: High — matches "Race condition with a realistic concurrent access
    pattern (probe vs IRQ, suspend vs ioctl, two CPUs hitting the same
    hot path) that produces corruption or crash" because the race is
    between the driver ioctl that records a failed range and the VMA
    fault path, and the result is the same slow-GUP crash path through
    pmd_page()/pud_page() on a memmap-less huge mapping. Proof:
    drivers/hv/mshv_vtl_main.c:1372-1382 adds the failed range and then
    calls unmap_mapping_pages(); drivers/hv/mshv_vtl_main.c:4009-4022
    and 4029-4032 perform the failed check before calling
    vmf_insert_pfn_pmd()/vmf_insert_pfn_pud(). The failed-list lookup
    is only protected for the duration of mshv_vtl_low_pfn_failed() by
    rcu_read_lock() at drivers/hv/mshv_vtl_main.c:1244-1251, and no
    state is held until insertion. mm/huge_memory.c:1425-1455 and
    1549-1574 show insert_pmd()/insert_pud() take the page-table lock
    and install the special huge entry without revalidating the
    driver's failed-range list. Thus CPU1 can observe no failed entry,
    CPU0 can add the failed entry and unmap nothing, and CPU1 can then
    install the huge raw-PFN entry after the zap.
    Suggested fix: (see Evidence)

Same practical mitigation as #1: the range's registration failed, so OpenHCL won't DMA/GUP it. Narrow window, no crash in practice. Could be hardened but low priority.

  1. [HIGH] The PUD raw-PFN path no longer requires PUD-order backing, so slow GUP can treat a 1G PUD mapping backed by smaller pgmap folios as one folio and corrupt pin/refcount accounting across the returned p…
    Sources: review-prompts
    Evidence: The PUD raw-PFN path no longer requires PUD-order backing, so slow GUP can
    treat a 1G PUD mapping backed by smaller pgmap folios as one
    folio and corrupt pin/refcount accounting across the returned
    pages.
    Impact: Severity: High — matches "Deterministic kernel crash (oops/BUG/panic/WARN) on
    a code path that any normal kernel use can reach (driver bind,
    suspend/resume, network packet, filesystem mount)" because pinning
    a large VTL0 mapping can put FOLL_PIN/FOLL_GET references on the
    wrong folio and later unpin different folios that never received
    those references, corrupting kernel page refcounts. Proof:
    drivers/hv/mshv_vtl_main.c:1350-1352 derives pgmap->vmemmap_shift
    from the submitted range alignment and does not require PUD_ORDER;
    drivers/hv/mshv_vtl_main.c:4026-4032 unconditionally calls
    vmf_insert_pfn_pud() after can_fault() and a base failed-range
    check. mm/gup.c:668-678 shows follow_huge_pud() grabs
    page_folio(page) once and sets page_mask = HPAGE_PUD_NR - 1.
    mm/gup.c:1459-1485 then uses that page_mask to batch the rest of
    the range and adds page_increm - 1 refs to page_folio(page), while
    mm/gup.c:1496-1499 returns page + j for all subpages. On release,
    mm/gup.c:421-422 groups the returned pages by each page's actual
    page_folio() and drops refs from those folios. For a PUD mapping
    assembled over smaller pgmap folios, the first folio receives the
    extra refs while later folios are returned and later unpinned
    without matching increments.
    Suggested fix: (see Evidence)

: Trying out the fix for this

  1. [MEDIUM] Failed-range entries can remain after the corresponding memory becomes registered, so valid registered memory can be permanently forced down the 4K fallback path and reintroduce the PageTables growth…
    Sources: review-prompts
    Evidence: Failed-range entries can remain after the corresponding memory becomes
    registered, so valid registered memory can be permanently forced
    down the 4K fallback path and reintroduce the PageTables growth
    this patch is meant to fix.
    Impact: Severity: Medium — matches "Logic error reachable only on uncommon paths —
    initialization-failure cleanup, hardware-error recovery,
    partial-suspend rollback" because it requires a prior failed
    registration, partial later success, or a duplicate-registration
    race, and the primary effect is persistent wrong fallback state
    rather than immediate memory corruption. Proof:
    drivers/hv/mshv_vtl_main.c:1287-1296 removes only failed entries
    fully contained in the successful [start_pfn,last_pfn) range, so a
    larger failed entry survives a smaller successful registration.
    drivers/hv/mshv_vtl_main.c:1329-1330 returns early for
    already-registered ranges before calling
    mshv_vtl_low_failed_clear(), so later idempotent calls cannot clear
    the stale failed marker. The duplicate race is also structurally
    possible: mm/memremap.c:156-160 returns -ENOMEM on a conflicting
    existing pgmap, while the success path in
    drivers/hv/mshv_vtl_main.c:1393 clears failed ranges before
    drivers/hv/mshv_vtl_main.c:1410-1412 publishes the registered
    range; a second caller can fail after that clear and add a failed
    entry via drivers/hv/mshv_vtl_main.c:1372-1375. Future huge faults
    then hit drivers/hv/mshv_vtl_main.c:4009-4010 or 4029-4030 and fall
    back despite valid registration.
    Suggested fix: (see Evidence)
    : Trying a fix for this

  2. [MEDIUM] The failed-range list can grow without bound from user-controlled failing registration requests, and every huge fault linearly scans that global list.
    Sources: review-prompts
    Evidence: The failed-range list can grow without bound from user-controlled failing
    registration requests, and every huge fault linearly scans that
    global list.
    Impact: Severity: Medium — matches "Logic error reachable only on uncommon paths —
    initialization-failure cleanup, hardware-error recovery,
    partial-suspend rollback" because entries are added on registration
    failure paths and then affect subsequent fault-time work. Proof:
    drivers/hv/mshv_vtl_main.c:1311 copies vtl0_mem from userspace and
    drivers/hv/mshv_vtl_main.c:1318-1321 only checks last_pfn >
    start_pfn. On devm_memremap_pages() failure,
    drivers/hv/mshv_vtl_main.c:1372-1375 records the range.
    drivers/hv/mshv_vtl_main.c:1267-1277 suppresses only entries fully
    covered by an existing one; it does not merge overlaps, cap the
    list, or reject adjacent/sliding ranges. mm/memremap.c:170-177
    shows user-selected System RAM or mixed ranges can fail with
    -ENXIO. Every PMD/PUD fault then calls mshv_vtl_low_pfn_failed(),
    whose drivers/hv/mshv_vtl_main.c:1244-1249 loop walks the entire
    failed list under RCU.
    Suggested fix: (see Evidence)
    : THis is for a privileged and trusted user like OpenVMM, so this problem is not relevant here.

  3. [MEDIUM] The idempotent registration check ignores the encrypted/decrypted attribute, so an opposite-attribute re-registration can incorrectly return success without applying the requested PGMAP_DECRYPTED dir…
    Sources: review-prompts
    Evidence: The idempotent registration check ignores the encrypted/decrypted attribute,
    so an opposite-attribute re-registration can incorrectly return
    success without applying the requested PGMAP_DECRYPTED direct-map
    attributes.
    Impact: Severity: Medium — matches "Logic error reachable only on uncommon paths —
    initialization-failure cleanup, hardware-error recovery,
    partial-suspend rollback" because it requires re-registering an
    already covered PFN range with the opposite DECRYPTED_MASK state,
    but then silently preserves the wrong memory attribute. Proof:
    drivers/hv/mshv_vtl_main.c:1315-1317 extracts DECRYPTED_MASK and
    strips it from start_pfn/last_pfn.
    drivers/hv/mshv_vtl_main.c:1227-1230 checks only stripped PFN
    coverage, and drivers/hv/mshv_vtl_main.c:1329-1330 returns success
    before creating a pgmap. The non-idempotent path is where the
    attribute is applied: drivers/hv/mshv_vtl_main.c:1341-1342 sets
    PGMAP_DECRYPTED, and mm/memremap.c:314-316 changes params.pgprot
    with pgprot_decrypted() only when that flag is set.
    Suggested fix: (see Evidence)
    : The same physical range is never registered as both encrypted and decrypted — there is only ever one attribute (encrypted). No code change required.

  4. [MEDIUM] The patch takes permanent folio_get() references on pgmap folios but never releases them before the devm_memremap_pages() resource is torn down on module exit.
    Sources: review-prompts
    Evidence: The patch takes permanent folio_get() references on pgmap folios but never
    releases them before the devm_memremap_pages() resource is torn
    down on module exit.
    Impact: Severity: Medium — matches "Resource leak with bounded blast radius (a few
    pages, a single file descriptor, one workqueue) on a rare path; not
    exploitable as a DoS" because it is a module-exit lifetime bug in a
    managed-resource cleanup path. Proof:
    drivers/hv/mshv_vtl_main.c:1403-1405 calls
    folio_get(pfn_folio(pfn)) for each registered pgmap folio, and the
    nearby comment at drivers/hv/mshv_vtl_main.c:1396-1401 states those
    refs are never dropped. drivers/hv/mshv_vtl_main.c:4267-4305 has no
    folio_put path and calls device_del(mem_dev) at line 4274 before
    range-list cleanup. The devres release path calls memunmap_pages():
    mm/memremap.c:132-135 defines devm_memremap_pages_release() as
    memunmap_pages(data), and mm/memremap.c:112-126 tears down the
    pgmap ranges. Thus the newly elevated page refs outlive the managed
    mapping lifetime.
    Suggested fix: (see Evidence)
    : as per design

  5. [MEDIUM] mshv_vtl_exit() reuses failed-range RCU list nodes on a local stale list before waiting for an RCU grace period, so an in-flight failed-range reader can have its traversal pointers overwritten.
    Sources: review-prompts
    Evidence: mshv_vtl_exit() reuses failed-range RCU list nodes on a local stale list
    before waiting for an RCU grace period, so an in-flight
    failed-range reader can have its traversal pointers overwritten.
    Impact: Severity: Medium — matches "Concurrency hazard that requires unusual
    scheduling to trigger (very narrow window, requires specific
    hardware behavior)" because it requires module teardown racing an
    existing RCU traversal, but the node reuse violates the RCU list
    lifetime rule and can corrupt traversal. Proof:
    drivers/hv/mshv_vtl_main.c:4295-4298 calls list_del_rcu(&r->list)
    and immediately list_add(&r->list, &stale) for
    mshv_vtl_low_failed_ranges; synchronize_rcu() is not called until
    drivers/hv/mshv_vtl_main.c:4302. include/linux/rculist.h:171-174
    states that after list_del_rcu(), synchronize_rcu() or call_rcu()
    must defer freeing until a grace period has elapsed, and
    include/linux/list.h:161-164 shows list_add overwrites
    new->next/new->prev. The failed-list reader at
    drivers/hv/mshv_vtl_main.c:1244-1249 uses
    list_for_each_entry_rcu(), so reusing r->list before the grace
    period can redirect an in-flight reader into the stack-local stale
    list.
    Suggested fix: (see Evidence)
    : Fixed

@namancse

Naman Jain (namancse) commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Fixed most of the relevant AI agent reviews, tried it again and repeated this cycle 4 times. Uploading the change now.

Under sustained VTL0 I/O the paravisor's /proc/meminfo PageTables grew
from ~660 kB into the tens of MB, steadily eating into VTL2 memory. The
low VTL0 mapping (/dev/mshv_vtl_low) was installing a 4K PTE for every
page it touched instead of a 2M PMD, so each touched 2M region cost a
full extra page table.

The regression came from gating the huge-fault path on a registered
struct page. Underhill registers lower-VTL memory with the kernel
lazily - only for ranges handed to a device for DMA (expose_va) - because
a small VTL2 cannot afford struct pages for all of guest RAM. The CPU
relay path, however, touches far more memory than is ever DMA'd, so most
huge faults hit not-yet-registered pfns, failed the gate, and fell back
to 4K.

Fix it by mapping huge VTL0 faults by raw pfn again, via
vmf_insert_pfn_pmd() with no registration gate (the pre-v6.15 behaviour).
vmf_insert_pfn_pmd() dereferences no struct page, so a 2M map is valid
even for an unregistered pfn and PageTables stays flat.

Going back to the raw-pfn path re-exposes issues that the recent
folio-based rework had addressed; handle each without giving up 2M:

  - rmap/RSS drift: vmf_insert_folio_pmd() adds a file rmap and RSS that
    zap_huge_pmd() never reverses on this VM_MIXEDMAP, non-DAX VMA
    (vma_is_special_huge() true, vma_is_dax() false), leaking a folio
    reference and tripping a "Bad rss-counter state" BUG. The pfn
    inserter carries no such state, so this drift simply goes away.

  - GUP refcount race: a huge pfn PMD holds no folio reference, so a
    zap racing pin_user_pages() could drop the refcount to 0 and warn
    in try_grab_folio(). Take a permanent reference on each pgmap folio
    in add_vtl0_mem() instead; VTL0 memory lives for the partition's
    lifetime, so the count never reaches 0.

  - GUP over smaller folios: add_vtl0_mem() derives the folio order
    from the range's alignment, so a sub-2M-aligned edge yields folios
    smaller than a PMD. Slow GUP would then batch a whole 2M span's
    references onto one base folio and corrupt its neighbours. Record
    such ranges on a normally-empty list - before the range itself is
    published - and fall back to 4K for any 2M window that overlaps one,
    so the mapping order never exceeds the folio order; only tiny
    RAM-edge tails lose 2M.

  - GUP into a memmap-less range: GUP on a huge pfn PMD walks the struct
    page (follow_huge_pmd -> pmd_page -> try_grab_folio) and would oops
    for a range whose devm_memremap_pages() failed. Track such failed
    ranges and fall back to 4K (pte_special) for any 2M window that
    overlaps one, so GUP fails gracefully with -EFAULT while all normal
    memory stays 2M.

Also make add_vtl0_mem() idempotent (an already-registered range returns
success, zapping any stale 4K PTEs so they refault as 2M) so
re-registration across servicing never reports a spurious failure or
leaves memory on 4K. Keep the failed-range bookkeeping consistent under
concurrent registration: re-check registration when recording a failure,
clear stale markers after the range is published so a transient -EBUSY
never strands valid memory on 4K, coalesce failed ranges so repeated
failures cannot grow the list without bound, and report success when a
concurrent add has already registered the range.

Fixes: 775741a ("Drivers: hv: mshv_vtl: use folio-aware inserters for huge VTL0 mappings")
Signed-off-by: Naman Jain <namjain@linux.microsoft.com>
Copilot AI review requested due to automatic review settings August 19, 2026 08:46
@namancse
Naman Jain (namancse) force-pushed the user/namjain/pagetablememfix branch from ec6650a to 9444981 Compare August 19, 2026 08:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (2)

drivers/hv/mshv_vtl_main.c:1349

  • mshv_vtl_low_failed_clear() only removes failed-range entries that are fully covered by [start_pfn,last_pfn). If a failed entry was coalesced to cover a larger span, and a later successful devm_memremap_pages() registers only a subrange, the remaining failed marker will still intersect that subrange and mshv_vtl_low_span_failed() will keep forcing 4K fallback for memory that is now registered (and GUP-safe). Consider trimming/splitting partially-overlapping failed entries on success so only truly memmap-less PFNs remain marked failed.
	spin_lock(&mshv_vtl_low_failed_lock);
	list_for_each_entry_safe(r, tmp, &mshv_vtl_low_failed_ranges, list) {
		if (r->start_pfn >= start_pfn && r->end_pfn <= last_pfn) {
			list_del_rcu(&r->list);
			kfree_rcu(r, rcu);
		}
	}

drivers/hv/mshv_vtl_main.c:1316

  • mshv_vtl_low_failed_add() skips recording a failed marker only when the requested span is fully covered by a single registered range. If add_vtl0_mem() is called with a span that partially overlaps already-registered memory and devm_memremap_pages() fails, this code can still record the whole span as failed, causing mshv_vtl_low_span_failed() to force 4K fallback even for PFNs that do have a memmap. It may be worth either rejecting overlapping/partially-registered registration requests up front, or subtracting already-registered subranges before recording a failed marker.

This issue also appears on line 1343 of the same file.

	spin_lock(&mshv_vtl_low_failed_lock);
	/*
	 * Skip if a concurrent registration already succeeded: it publishes the
	 * range and then clears failed markers, so recording one now (after that
	 * clear) would strand valid memory on the 4K path. The success path
	 * clears under this same lock, so the check and the add are ordered.
	 */
	if (mshv_vtl_low_range_registered(range->start_pfn, range->end_pfn)) {
		spin_unlock(&mshv_vtl_low_failed_lock);
		return false;
	}

@hargar19
Hardik Garg (hargar19) merged commit 78489eb into product/hcl-main/6.18 Aug 21, 2026
30 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants