Re: [PATCH v1 05/11] mm: convert VM_PFNMAP tracking to pfnmap_track() + pfnmap_untrack()

David Hildenbrand
2025-05-05T13:00:12Z

>>
>> This change implies that we'll keep tracking the original PFN range even
>> after splitting + partially unmapping it: not too bad, because it was
>> not working reliably before. The only thing that kind-of worked before
>> was shrinking such a mapping using mremap(): we managed to adjust the
>> reservation in a hacky way, now we won't adjust the reservation but
>> leave it around until all involved VMAs are gone.
>
> Hm, but what if we shrink a VMA, then map another one, might it be
> incorrectly storing PAT attributes for part of the range that is now mapped
> elsewhere?

Not "incorrectly". We'll simply undo the reservation of the cachemode
for the original PFN range once everything of the original VMA is gone.

AFAIK, one can usually mmap() the "unmapped" part after shrinking again
with the same cachemode, which should be the main use case.

Supporting partial un-tracking will require hooking into vma splitting
code ... not something I am super happy about. :)

>
> Also my god re: the 'kind of working' aspects of PAT, so frustrating.
>
>>
>> Signed-off-by: David Hildenbrand <david@redhat.com>
>
> Generally looking good, afaict, but maybe let's get some input from Suren
> on VMA size.
>
> Are there actually any PAT tests out here? I had a quick glance in
> tools/testing/selftests/x86,mm and couldn't find any, but didn't look
> _that_ card.

Heh, booting a simple VM gets PAT involved. I suspect because /dev/mem
and BIOS/GPU/whatever hacks.

In the cover letter I have

"Briefly tested with some basic /dev/mem test I crafted. I want to
convert them to selftests, but that might or might not require a bit of
more work (e.g., /dev/mem accessibility)."

>
> Thanks in general for tackling this, this is a big improvement!
>
>> ---
>> include/linux/mm_inline.h | 2 +
>> include/linux/mm_types.h | 11 ++++++
>> kernel/fork.c | 54 ++++++++++++++++++++++++--
>> mm/memory.c | 81 +++++++++++++++++++++++++++++++--------
>> mm/mremap.c | 4 --
>> 5 files changed, 128 insertions(+), 24 deletions(-)
>>
>> diff --git a/include/linux/mm_inline.h b/include/linux/mm_inline.h
>> index f9157a0c42a5c..89b518ff097e6 100644
>> --- a/include/linux/mm_inline.h
>> +++ b/include/linux/mm_inline.h
>> @@ -447,6 +447,8 @@ static inline bool anon_vma_name_eq(struct anon_vma_name *anon_name1,
>>
>> #endif /* CONFIG_ANON_VMA_NAME */
>>
>> +void pfnmap_track_ctx_release(struct kref *ref);
>> +
>> static inline void init_tlb_flush_pending(struct mm_struct *mm)
>> {
>> atomic_set(&mm->tlb_flush_pending, 0);
>> diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
>> index 56d07edd01f91..91124761cfda8 100644
>> --- a/include/linux/mm_types.h
>> +++ b/include/linux/mm_types.h
>> @@ -764,6 +764,14 @@ struct vma_numab_state {
>> int prev_scan_seq;
>> };
>>
>> +#ifdef __HAVE_PFNMAP_TRACKING
>> +struct pfnmap_track_ctx {
>> + struct kref kref;
>> + unsigned long pfn;
>> + unsigned long size;
>
> Again, (super) nitty, but we really should express units. I suppose 'size'
> implies bytes to be honest as you'd unlikely say 'size' for number of pages
> (you'd go with nr_pages or something). But maybe a trailing /* in bytes */
> would help.
>
> Not a big deal though!

"size" in the kernel is usually bytes, never pages ... but I might be wrong.

Anyhow, I can use "/* in bytes*/" here, although I doubt that many will
benefit from this comment :)

>
>> +};
>> +#endif
>> +
>> /*
>> * This struct describes a virtual memory area. There is one of these
>> * per VM-area/task. A VM area is any part of the process virtual memory
>> @@ -877,6 +885,9 @@ struct vm_area_struct {
>> struct anon_vma_name *anon_name;
>> #endif
>> struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
>> +#ifdef __HAVE_PFNMAP_TRACKING
>
> An aside, but absolutely hate '__HAVE_PFNMAP_TRACKING' as a name here. But
> you didn't create it, and it's not really sensible to change it in this
> series so. Just a rumble...

I cannot argue with that ... same here.

To be clear: I hate all of this with passion ;) With this series, I hate
it a bit less.

[...]

>
> Obviously my series will break this but should be _fairly_ trivial to
> update.
>
> You will however have to make sure to update tools/testing/vma/* to handle
> the new functions in userland testing (they need to be stubbed otu).

Ah, I was happy it compiled but looks like I'll have to mess with that
as well.

>
> If it makes life easier, you can even send it to me off-list, or just send
> it without changing this in a respin and I can fix it up fairly quick for
> you.

Let me give it a try first, I'll let you know if it takes me too long.

Thanks!

[...]

>> /**
>> * remap_pfn_range - remap kernel memory to userspace
>> * @vma: user vma to map to
>> @@ -2883,20 +2902,50 @@ int remap_pfn_range_notrack(struct vm_area_struct *vma, unsigned long addr,
>> *
>> * Return: %0 on success, negative error code otherwise.
>> */
>> +#ifdef __HAVE_PFNMAP_TRACKING
>> int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
>> unsigned long pfn, unsigned long size, pgprot_t prot)
>
> OK so to expose some of my lack-of-knowledge of PAT - is this the
> 'entrypoint' to PAT tracking?

Only if you're using remap_pfn_range() ... there is other low-level
tracking/reservation using the memtype_reserve() interface and friends.

>
> So we have some kernel memory we remap to userland as PFN map, the kind
> that very well might be sensible to use PAT the change cache behaviour for,
> and each time this happens, it's mapped as PAT?

Right, anytime someone uses remap_pfn_range() on the full VMA, we track
it (depending on RAM vs. !RAM this "tracking" has different semantics).

For RAM, we seem to only lookup the cachemode. For !RAM, we seem to
reserve the memtype for the PFN range, which will fail if there already
is an incompatible memtype reserved.

It's all ... very weird.

--
Cheers,

David / dhildenb