mm: soft-dirty bits for user memory changes tracking
[platform/adaptation/renesas_rcar/renesas_kernel.git] / fs / proc / task_mmu.c
index 39d6412..a18e065 100644 (file)
@@ -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;