nouveau/vmm: start tracking if the LPT PTE is valid. (v6)

When NVK enabled large pages userspace tests were seeing fault
reports at a valid address.

There was a case where an address moving from 64k page to 4k pages
could expose a race between unmapping the 4k page, mapping the 64k
page and unref the 4k pages.

Unref 4k pages would cause the dual-page table handling to always
set the LPTE entry to SPARSE or INVALID, but if we'd mapped a valid
LPTE in the meantime, it would get trashed. Keep track of when
a valid LPTE has been referenced, and don't reset in that case.

This adds an lpte valid tracker and lpte reference count.

Whenever an lpte is referenced, it gets made valid and the ref count
increases, whenever it gets unreference the refcount is tracked.

Link: https://gitlab.freedesktop.org/mesa/mesa/-/issues/14610
Reviewed-by: Mary Guillemard <mary@mary.zone>
Tested-by: Mary Guillemard <mary@mary.zone>
Tested-by: Mel Henning <mhenning@darkrefraction.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
Link: https://patch.msgid.link/20260204030208.2313241-4-airlied@gmail.com
This commit is contained in:
Dave Airlie 2026-02-04 13:00:07 +10:00
parent 9dc983a85e
commit d19512f5ab
3 changed files with 36 additions and 10 deletions

View File

@ -10,7 +10,7 @@
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 4
#define DRIVER_PATCHLEVEL 1
#define DRIVER_PATCHLEVEL 2
/*
* 1.1.1:
@ -37,6 +37,8 @@
* - implemented limited ABI16/NVIF interop
* 1.4.1:
* - add variable page sizes and compression for Turing+
* 1.4.2:
* - tell userspace LPTE/SPTE races are fixed.
*/
#include <linux/notifier.h>

View File

@ -242,14 +242,17 @@ nvkm_vmm_unref_sptes(struct nvkm_vmm_iter *it, struct nvkm_vmm_pt *pgt,
if (pgt->pte[pteb].s.sparse) {
TRA(it, "LPTE %05x: U -> S %d PTEs", pteb, ptes);
pair->func->sparse(vmm, pgt->pt[0], pteb, ptes);
} else
if (pair->func->invalid) {
/* If the MMU supports it, restore the LPTE to the
* INVALID state to tell the MMU there is no point
* trying to fetch the corresponding SPTEs.
*/
TRA(it, "LPTE %05x: U -> I %d PTEs", pteb, ptes);
pair->func->invalid(vmm, pgt->pt[0], pteb, ptes);
} else if (!pgt->pte[pteb].s.lpte_valid) {
if (pair->func->invalid) {
/* If the MMU supports it, restore the LPTE to the
* INVALID state to tell the MMU there is no point
* trying to fetch the corresponding SPTEs.
*/
TRA(it, "LPTE %05x: U -> I %d PTEs", pteb, ptes);
pair->func->invalid(vmm, pgt->pt[0], pteb, ptes);
}
} else {
TRA(it, "LPTE %05x: V %d PTEs", pteb, ptes);
}
}
}
@ -280,6 +283,15 @@ nvkm_vmm_unref_ptes(struct nvkm_vmm_iter *it, bool pfn, u32 ptei, u32 ptes)
if (desc->type == SPT && (pgt->refs[0] || pgt->refs[1]))
nvkm_vmm_unref_sptes(it, pgt, desc, ptei, ptes);
if (desc->type == LPT && (pgt->refs[0] || pgt->refs[1])) {
for (u32 lpti = ptei; ptes; lpti++) {
pgt->pte[lpti].s.lptes--;
if (pgt->pte[lpti].s.lptes == 0)
pgt->pte[lpti].s.lpte_valid = false;
ptes--;
}
}
/* PT no longer needed? Destroy it. */
if (!pgt->refs[type]) {
it->lvl++;
@ -332,10 +344,12 @@ nvkm_vmm_ref_sptes(struct nvkm_vmm_iter *it, struct nvkm_vmm_pt *pgt,
* Determine how many LPTEs need to transition state.
*/
pgt->pte[ptei].s.spte_valid = true;
pgt->pte[ptei].s.lpte_valid = false;
for (ptes = 1, ptei++; ptei < lpti; ptes++, ptei++) {
if (pgt->pte[ptei].s.spte_valid)
break;
pgt->pte[ptei].s.spte_valid = true;
pgt->pte[ptei].s.lpte_valid = false;
}
if (pgt->pte[pteb].s.sparse) {
@ -374,6 +388,15 @@ nvkm_vmm_ref_ptes(struct nvkm_vmm_iter *it, bool pfn, u32 ptei, u32 ptes)
if (desc->type == SPT)
nvkm_vmm_ref_sptes(it, pgt, desc, ptei, ptes);
if (desc->type == LPT) {
for (u32 lpti = ptei; ptes; lpti++) {
pgt->pte[lpti].s.spte_valid = false;
pgt->pte[lpti].s.lpte_valid = true;
pgt->pte[lpti].s.lptes++;
ptes--;
}
}
return true;
}

View File

@ -9,7 +9,8 @@ union nvkm_pte_tracker {
struct {
u32 sparse:1;
u32 spte_valid:1;
u32 padding:14;
u32 lpte_valid:1;
u32 lptes:13;
u32 sptes:16;
} s;
};