This function provides the virtual equivalent of linear_page_index(),
instead offsetting based on the virtual page offset of the VMA.

It is valid only for anonymous or MAP_PRIVATE file-backed mappings. It must
not be called for shared file-backed mappings.

For pure anon VMAs, this will be equal to linear_page_index().

We implement the algorithm in __linear_virt_page_index(), which is provided
for internal mm code that might be interacting with shared VMAs.

In linear_virt_page_index() we assert that both of these invariants are
true.

Note that MAP_PRIVATE-/dev/zero mappings will satisfy vma_is_anonymous()
but not fulfill this invariant, so when asserting this we check
vma->vm_file to account for this.

We do not update callsites yet, so no functional change intended.

Also const-ify vma_is_anonymous() to make it compatible with the
const-ified linear_virt_page_index().

VMA userland tests are updated accordingly.

Signed-off-by: Lorenzo Stoakes (ARM) <[email protected]>
---
 include/linux/mm.h              |  2 +-
 include/linux/pagemap.h         | 42 +++++++++++++++++++++++++++++++++++++++++
 tools/testing/vma/include/dup.h | 25 +++++++++++++++++++++++-
 3 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 59b98cc60402..b6503b5f0010 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1556,7 +1556,7 @@ static inline void vma_desc_set_anonymous(struct 
vm_area_desc *desc)
        desc->vm_ops = NULL;
 }
 
-static inline bool vma_is_anonymous(struct vm_area_struct *vma)
+static inline bool vma_is_anonymous(const struct vm_area_struct *vma)
 {
        return !vma->vm_ops;
 }
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index c6fc783aaee5..c9c65ba60ab3 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -1101,6 +1101,48 @@ static inline pgoff_t linear_page_index(const struct 
vm_area_struct *vma,
        return pgoff;
 }
 
+static inline pgoff_t __linear_virt_page_index(const struct vm_area_struct 
*vma,
+                                              const unsigned long address)
+{
+       pgoff_t pgoff;
+
+       pgoff = linear_page_delta(vma, address);
+       pgoff += vma_start_virt_pgoff(vma);
+       return pgoff;
+}
+
+/**
+ * linear_virt_page_index() - Determine the absolute virtual page offset of
+ * @address within @vma.
+ * @vma: An anonymous or MAP_PRIVATE file-backed VMA in which @address resides.
+ * @address: The address whose absolute page offset is required.
+ *
+ * This returns the virtual page offset of @address, which is the page offset
+ * the address possessed at the time the VMA was first faulted.
+ *
+ * For anonymous mappings, this returns the same value as linear_page_index().
+ *
+ * For MAP_PRIVATE file-backed mappings, this returns the virtual page offset 
of
+ * @address, which is the page offset the address possessed at the time the VMA
+ * was first faulted.
+ *
+ * It is not valid to call this function for shared file-backed mappings.
+ *
+ * Returns: The absolute virtual page offset of @address within @vma.
+ */
+static inline pgoff_t linear_virt_page_index(const struct vm_area_struct *vma,
+                                            const unsigned long address)
+{
+       const pgoff_t pgoff = __linear_virt_page_index(vma, address);
+
+       VM_WARN_ON_ONCE(vma_test(vma, VMA_SHARED_BIT));
+       /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
+       if (vma_is_anonymous(vma) && !vma->vm_file)
+               VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
+
+       return pgoff;
+}
+
 struct wait_page_key {
        struct folio *folio;
        int bit_nr;
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index 00d416d79ec1..0c74899abb33 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -1417,7 +1417,7 @@ static inline void vma_iter_set(struct vma_iterator *vmi, 
unsigned long addr)
        mas_set(&vmi->mas, addr);
 }
 
-static inline bool vma_is_anonymous(struct vm_area_struct *vma)
+static inline bool vma_is_anonymous(const struct vm_area_struct *vma)
 {
        return !vma->vm_ops;
 }
@@ -1610,3 +1610,26 @@ static inline pgprot_t vma_get_page_prot(const struct 
vm_area_struct *vma)
 {
        return vma_flags_to_page_prot(vma->flags);
 }
+
+static inline pgoff_t __linear_virt_page_index(const struct vm_area_struct 
*vma,
+                                              const unsigned long address)
+{
+       pgoff_t pgoff;
+
+       pgoff = linear_page_delta(vma, address);
+       pgoff += vma_start_virt_pgoff(vma);
+       return pgoff;
+}
+
+static inline pgoff_t linear_virt_page_index(const struct vm_area_struct *vma,
+                                            const unsigned long address)
+{
+       const pgoff_t pgoff = __linear_virt_page_index(vma, address);
+
+       VM_WARN_ON_ONCE(vma_test(vma, VMA_SHARED_BIT));
+       /* Account for MAP_PRIVATE-/dev/zero which is only semi-anonymous. */
+       if (vma_is_anonymous(vma) && !vma->vm_file)
+               VM_WARN_ON_ONCE(pgoff != linear_page_index(vma, address));
+
+       return pgoff;
+}

-- 
2.55.0


Reply via email to