|
@@ -11,6 +11,7 @@
|
|
|
#include <linux/rmap.h>
|
|
|
#include <linux/swap.h>
|
|
|
#include <linux/swapops.h>
|
|
|
+#include <linux/mmu_notifier.h>
|
|
|
|
|
|
#include <asm/elf.h>
|
|
|
#include <asm/uaccess.h>
|
|
@@ -692,13 +693,32 @@ enum clear_refs_types {
|
|
|
CLEAR_REFS_ALL = 1,
|
|
|
CLEAR_REFS_ANON,
|
|
|
CLEAR_REFS_MAPPED,
|
|
|
+ CLEAR_REFS_SOFT_DIRTY,
|
|
|
CLEAR_REFS_LAST,
|
|
|
};
|
|
|
|
|
|
struct clear_refs_private {
|
|
|
struct vm_area_struct *vma;
|
|
|
+ enum clear_refs_types type;
|
|
|
};
|
|
|
|
|
|
+static inline void clear_soft_dirty(struct vm_area_struct *vma,
|
|
|
+ unsigned long addr, pte_t *pte)
|
|
|
+{
|
|
|
+#ifdef CONFIG_MEM_SOFT_DIRTY
|
|
|
+ /*
|
|
|
+ * The soft-dirty tracker uses #PF-s to catch writes
|
|
|
+ * to pages, so write-protect the pte as well. See the
|
|
|
+ * Documentation/vm/soft-dirty.txt for full description
|
|
|
+ * of how soft-dirty works.
|
|
|
+ */
|
|
|
+ pte_t ptent = *pte;
|
|
|
+ ptent = pte_wrprotect(ptent);
|
|
|
+ ptent = pte_clear_flags(ptent, _PAGE_SOFT_DIRTY);
|
|
|
+ set_pte_at(vma->vm_mm, addr, pte, ptent);
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
static int clear_refs_pte_range(pmd_t *pmd, unsigned long addr,
|
|
|
unsigned long end, struct mm_walk *walk)
|
|
|
{
|
|
@@ -718,6 +738,11 @@ static int clear_refs_pte_range(pmd_t *pmd, unsigned long addr,
|
|
|
if (!pte_present(ptent))
|
|
|
continue;
|
|
|
|
|
|
+ if (cp->type == CLEAR_REFS_SOFT_DIRTY) {
|
|
|
+ clear_soft_dirty(vma, addr, pte);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
page = vm_normal_page(vma, addr, ptent);
|
|
|
if (!page)
|
|
|
continue;
|
|
@@ -759,6 +784,7 @@ static ssize_t clear_refs_write(struct file *file, const char __user *buf,
|
|
|
mm = get_task_mm(task);
|
|
|
if (mm) {
|
|
|
struct clear_refs_private cp = {
|
|
|
+ .type = type,
|
|
|
};
|
|
|
struct mm_walk clear_refs_walk = {
|
|
|
.pmd_entry = clear_refs_pte_range,
|
|
@@ -766,6 +792,8 @@ static ssize_t clear_refs_write(struct file *file, const char __user *buf,
|
|
|
.private = &cp,
|
|
|
};
|
|
|
down_read(&mm->mmap_sem);
|
|
|
+ if (type == CLEAR_REFS_SOFT_DIRTY)
|
|
|
+ mmu_notifier_invalidate_range_start(mm, 0, -1);
|
|
|
for (vma = mm->mmap; vma; vma = vma->vm_next) {
|
|
|
cp.vma = vma;
|
|
|
if (is_vm_hugetlb_page(vma))
|
|
@@ -786,6 +814,8 @@ static ssize_t clear_refs_write(struct file *file, const char __user *buf,
|
|
|
walk_page_range(vma->vm_start, vma->vm_end,
|
|
|
&clear_refs_walk);
|
|
|
}
|
|
|
+ if (type == CLEAR_REFS_SOFT_DIRTY)
|
|
|
+ mmu_notifier_invalidate_range_end(mm, 0, -1);
|
|
|
flush_tlb_mm(mm);
|
|
|
up_read(&mm->mmap_sem);
|
|
|
mmput(mm);
|
|
@@ -827,6 +857,7 @@ struct pagemapread {
|
|
|
/* in "new" pagemap pshift bits are occupied with more status bits */
|
|
|
#define PM_STATUS2(v2, x) (__PM_PSHIFT(v2 ? x : PAGE_SHIFT))
|
|
|
|
|
|
+#define __PM_SOFT_DIRTY (1LL)
|
|
|
#define PM_PRESENT PM_STATUS(4LL)
|
|
|
#define PM_SWAP PM_STATUS(2LL)
|
|
|
#define PM_FILE PM_STATUS(1LL)
|
|
@@ -868,6 +899,7 @@ static void pte_to_pagemap_entry(pagemap_entry_t *pme, struct pagemapread *pm,
|
|
|
{
|
|
|
u64 frame, flags;
|
|
|
struct page *page = NULL;
|
|
|
+ int flags2 = 0;
|
|
|
|
|
|
if (pte_present(pte)) {
|
|
|
frame = pte_pfn(pte);
|
|
@@ -888,13 +920,15 @@ static void pte_to_pagemap_entry(pagemap_entry_t *pme, struct pagemapread *pm,
|
|
|
|
|
|
if (page && !PageAnon(page))
|
|
|
flags |= PM_FILE;
|
|
|
+ if (pte_soft_dirty(pte))
|
|
|
+ flags2 |= __PM_SOFT_DIRTY;
|
|
|
|
|
|
- *pme = make_pme(PM_PFRAME(frame) | PM_STATUS2(pm->v2, 0) | flags);
|
|
|
+ *pme = make_pme(PM_PFRAME(frame) | PM_STATUS2(pm->v2, flags2) | flags);
|
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
|
|
static void thp_pmd_to_pagemap_entry(pagemap_entry_t *pme, struct pagemapread *pm,
|
|
|
- pmd_t pmd, int offset)
|
|
|
+ pmd_t pmd, int offset, int pmd_flags2)
|
|
|
{
|
|
|
/*
|
|
|
* Currently pmd for thp is always present because thp can not be
|
|
@@ -903,13 +937,13 @@ static void thp_pmd_to_pagemap_entry(pagemap_entry_t *pme, struct pagemapread *p
|
|
|
*/
|
|
|
if (pmd_present(pmd))
|
|
|
*pme = make_pme(PM_PFRAME(pmd_pfn(pmd) + offset)
|
|
|
- | PM_STATUS2(pm->v2, 0) | PM_PRESENT);
|
|
|
+ | PM_STATUS2(pm->v2, pmd_flags2) | PM_PRESENT);
|
|
|
else
|
|
|
*pme = make_pme(PM_NOT_PRESENT(pm->v2));
|
|
|
}
|
|
|
#else
|
|
|
static inline void thp_pmd_to_pagemap_entry(pagemap_entry_t *pme, struct pagemapread *pm,
|
|
|
- pmd_t pmd, int offset)
|
|
|
+ pmd_t pmd, int offset, int pmd_flags2)
|
|
|
{
|
|
|
}
|
|
|
#endif
|
|
@@ -926,12 +960,15 @@ static int pagemap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
|
|
|
/* find the first VMA at or above 'addr' */
|
|
|
vma = find_vma(walk->mm, addr);
|
|
|
if (vma && pmd_trans_huge_lock(pmd, vma) == 1) {
|
|
|
+ int pmd_flags2;
|
|
|
+
|
|
|
+ pmd_flags2 = (pmd_soft_dirty(*pmd) ? __PM_SOFT_DIRTY : 0);
|
|
|
for (; addr != end; addr += PAGE_SIZE) {
|
|
|
unsigned long offset;
|
|
|
|
|
|
offset = (addr & ~PAGEMAP_WALK_MASK) >>
|
|
|
PAGE_SHIFT;
|
|
|
- thp_pmd_to_pagemap_entry(&pme, pm, *pmd, offset);
|
|
|
+ thp_pmd_to_pagemap_entry(&pme, pm, *pmd, offset, pmd_flags2);
|
|
|
err = add_to_pagemap(addr, &pme, pm);
|
|
|
if (err)
|
|
|
break;
|