---
include/linux/mm.h | 2 ++
mm/gup.c | 24 ++++++++++++++++++++++++
mm/huge_memory.c | 8 ++++++++
3 files changed, 34 insertions(+)
@@ -1084,6 +1084,8 @@ struct zap_details {
struct page *vm_normal_page(struct vm_area_struct *vma, unsigned long addr,
pte_t pte);
+int follow_pfn_pmd(struct vm_area_struct *vma, unsigned long address,
+ pmd_t *pmd, unsigned int flags);
int zap_vma_ptes(struct vm_area_struct *vma, unsigned long address,
unsigned long size);
@@ -34,6 +34,30 @@ static struct page *no_page_table(struct vm_area_struct *vma,
return NULL;
}
+int follow_pfn_pmd(struct vm_area_struct *vma, unsigned long address,
+ pmd_t *pmd, unsigned int flags)
+{
+ /* No page to get reference */
+ if (flags & FOLL_GET)
+ return -EFAULT;
+
+ if (flags & FOLL_TOUCH) {
+ pmd_t entry = *pmd;
+
+ if (flags & FOLL_WRITE)
+ entry = pmd_mkdirty(entry);
+ entry = pmd_mkyoung(entry);
+
+ if (!pmd_same(*pmd, entry)) {
+ set_pmd_at(vma->vm_mm, address, pmd, entry);
+ update_mmu_cache_pmd(vma, address, pmd);
+ }
+ }
+
+ /* Proper page table entry exists, but no corresponding struct page */
+ return -EEXIST;
+}
+
static int follow_pfn_pte(struct vm_area_struct *vma, unsigned long address,
pte_t *pte, unsigned int flags)
{
@@ -1276,6 +1276,7 @@ struct page *follow_trans_huge_pmd(struct vm_area_struct *vma,
{
struct mm_struct *mm = vma->vm_mm;
struct page *page = NULL;
+ int ret;
assert_spin_locked(pmd_lockptr(mm, pmd));
@@ -1290,6 +1291,13 @@ struct page *follow_trans_huge_pmd(struct vm_area_struct *vma,
if ((flags & FOLL_NUMA) && pmd_protnone(*pmd))
goto out;
+ /* pfn map does not have struct page */
+ if (vma->vm_flags & (VM_PFNMAP | VM_MIXEDMAP)) {
+ ret = follow_pfn_pmd(vma, addr, pmd, flags);
+ page = ERR_PTR(ret);
+ goto out;
+ }
+
page = pmd_page(*pmd);
VM_BUG_ON_PAGE(!PageHead(page), page);
if (flags & FOLL_TOUCH) {