Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | // SPDX-License-Identifier: GPL-2.0 #include <linux/pagewalk.h> #include <linux/hugetlb.h> #include <linux/bitops.h> #include <linux/mmu_notifier.h> #include <asm/cacheflush.h> #include <asm/tlbflush.h> /** * struct wp_walk - Private struct for pagetable walk callbacks * @range: Range for mmu notifiers * @tlbflush_start: Address of first modified pte * @tlbflush_end: Address of last modified pte + 1 * @total: Total number of modified ptes */ struct wp_walk { struct mmu_notifier_range range; unsigned long tlbflush_start; unsigned long tlbflush_end; unsigned long total; }; /** * wp_pte - Write-protect a pte * @pte: Pointer to the pte * @addr: The start of protecting virtual address * @end: The end of protecting virtual address * @walk: pagetable walk callback argument * * The function write-protects a pte and records the range in * virtual address space of touched ptes for efficient range TLB flushes. */ static int wp_pte(pte_t *pte, unsigned long addr, unsigned long end, struct mm_walk *walk) { struct wp_walk *wpwalk = walk->private; pte_t ptent = *pte; if (pte_write(ptent)) { pte_t old_pte = ptep_modify_prot_start(walk->vma, addr, pte); ptent = pte_wrprotect(old_pte); ptep_modify_prot_commit(walk->vma, addr, pte, old_pte, ptent); wpwalk->total++; wpwalk->tlbflush_start = min(wpwalk->tlbflush_start, addr); wpwalk->tlbflush_end = max(wpwalk->tlbflush_end, addr + PAGE_SIZE); } return 0; } /** * struct clean_walk - Private struct for the clean_record_pte function. * @base: struct wp_walk we derive from * @bitmap_pgoff: Address_space Page offset of the first bit in @bitmap * @bitmap: Bitmap with one bit for each page offset in the address_space range * covered. * @start: Address_space page offset of first modified pte relative * to @bitmap_pgoff * @end: Address_space page offset of last modified pte relative * to @bitmap_pgoff */ struct clean_walk { struct wp_walk base; pgoff_t bitmap_pgoff; unsigned long *bitmap; pgoff_t start; pgoff_t end; }; #define to_clean_walk(_wpwalk) container_of(_wpwalk, struct clean_walk, base) /** * clean_record_pte - Clean a pte and record its address space offset in a * bitmap * @pte: Pointer to the pte * @addr: The start of virtual address to be clean * @end: The end of virtual address to be clean * @walk: pagetable walk callback argument * * The function cleans a pte and records the range in * virtual address space of touched ptes for efficient TLB flushes. * It also records dirty ptes in a bitmap representing page offsets * in the address_space, as well as the first and last of the bits * touched. */ static int clean_record_pte(pte_t *pte, unsigned long addr, unsigned long end, struct mm_walk *walk) { struct wp_walk *wpwalk = walk->private; struct clean_walk *cwalk = to_clean_walk(wpwalk); pte_t ptent = *pte; if (pte_dirty(ptent)) { pgoff_t pgoff = ((addr - walk->vma->vm_start) >> PAGE_SHIFT) + walk->vma->vm_pgoff - cwalk->bitmap_pgoff; pte_t old_pte = ptep_modify_prot_start(walk->vma, addr, pte); ptent = pte_mkclean(old_pte); ptep_modify_prot_commit(walk->vma, addr, pte, old_pte, ptent); wpwalk->total++; wpwalk->tlbflush_start = min(wpwalk->tlbflush_start, addr); wpwalk->tlbflush_end = max(wpwalk->tlbflush_end, addr + PAGE_SIZE); __set_bit(pgoff, cwalk->bitmap); cwalk->start = min(cwalk->start, pgoff); cwalk->end = max(cwalk->end, pgoff + 1); } return 0; } /* * wp_clean_pmd_entry - The pagewalk pmd callback. * * Dirty-tracking should take place on the PTE level, so * WARN() if encountering a dirty huge pmd. * Furthermore, never split huge pmds, since that currently * causes dirty info loss. The pagefault handler should do * that if needed. */ static int wp_clean_pmd_entry(pmd_t *pmd, unsigned long addr, unsigned long end, struct mm_walk *walk) { pmd_t pmdval = pmd_read_atomic(pmd); if (!pmd_trans_unstable(&pmdval)) return 0; if (pmd_none(pmdval)) { walk->action = ACTION_AGAIN; return 0; } /* Huge pmd, present or migrated */ walk->action = ACTION_CONTINUE; if (pmd_trans_huge(pmdval) || pmd_devmap(pmdval)) WARN_ON(pmd_write(pmdval) || pmd_dirty(pmdval)); return 0; } /* * wp_clean_pud_entry - The pagewalk pud callback. * * Dirty-tracking should take place on the PTE level, so * WARN() if encountering a dirty huge puds. * Furthermore, never split huge puds, since that currently * causes dirty info loss. The pagefault handler should do * that if needed. */ static int wp_clean_pud_entry(pud_t *pud, unsigned long addr, unsigned long end, struct mm_walk *walk) { pud_t pudval = READ_ONCE(*pud); if (!pud_trans_unstable(&pudval)) return 0; if (pud_none(pudval)) { walk->action = ACTION_AGAIN; return 0; } #ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD /* Huge pud */ walk->action = ACTION_CONTINUE; if (pud_trans_huge(pudval) || pud_devmap(pudval)) WARN_ON(pud_write(pudval) || pud_dirty(pudval)); #endif return 0; } /* * wp_clean_pre_vma - The pagewalk pre_vma callback. * * The pre_vma callback performs the cache flush, stages the tlb flush * and calls the necessary mmu notifiers. */ static int wp_clean_pre_vma(unsigned long start, unsigned long end, struct mm_walk *walk) { struct wp_walk *wpwalk = walk->private; wpwalk->tlbflush_start = end; wpwalk->tlbflush_end = start; mmu_notifier_range_init(&wpwalk->range, MMU_NOTIFY_PROTECTION_PAGE, 0, walk->vma, walk->mm, start, end); mmu_notifier_invalidate_range_start(&wpwalk->range); flush_cache_range(walk->vma, start, end); /* * We're not using tlb_gather_mmu() since typically * only a small subrange of PTEs are affected, whereas * tlb_gather_mmu() records the full range. */ inc_tlb_flush_pending(walk->mm); return 0; } /* * wp_clean_post_vma - The pagewalk post_vma callback. * * The post_vma callback performs the tlb flush and calls necessary mmu * notifiers. */ static void wp_clean_post_vma(struct mm_walk *walk) { struct wp_walk *wpwalk = walk->private; if (mm_tlb_flush_nested(walk->mm)) flush_tlb_range(walk->vma, wpwalk->range.start, wpwalk->range.end); else if (wpwalk->tlbflush_end > wpwalk->tlbflush_start) flush_tlb_range(walk->vma, wpwalk->tlbflush_start, wpwalk->tlbflush_end); mmu_notifier_invalidate_range_end(&wpwalk->range); dec_tlb_flush_pending(walk->mm); } /* * wp_clean_test_walk - The pagewalk test_walk callback. * * Won't perform dirty-tracking on COW, read-only or HUGETLB vmas. */ static int wp_clean_test_walk(unsigned long start, unsigned long end, struct mm_walk *walk) { unsigned long vm_flags = READ_ONCE(walk->vma->vm_flags); /* Skip non-applicable VMAs */ if ((vm_flags & (VM_SHARED | VM_MAYWRITE | VM_HUGETLB)) != (VM_SHARED | VM_MAYWRITE)) return 1; return 0; } static const struct mm_walk_ops clean_walk_ops = { .pte_entry = clean_record_pte, .pmd_entry = wp_clean_pmd_entry, .pud_entry = wp_clean_pud_entry, .test_walk = wp_clean_test_walk, .pre_vma = wp_clean_pre_vma, .post_vma = wp_clean_post_vma }; static const struct mm_walk_ops wp_walk_ops = { .pte_entry = wp_pte, .pmd_entry = wp_clean_pmd_entry, .pud_entry = wp_clean_pud_entry, .test_walk = wp_clean_test_walk, .pre_vma = wp_clean_pre_vma, .post_vma = wp_clean_post_vma }; /** * wp_shared_mapping_range - Write-protect all ptes in an address space range * @mapping: The address_space we want to write protect * @first_index: The first page offset in the range * @nr: Number of incremental page offsets to cover * * Note: This function currently skips transhuge page-table entries, since * it's intended for dirty-tracking on the PTE level. It will warn on * encountering transhuge write-enabled entries, though, and can easily be * extended to handle them as well. * * Return: The number of ptes actually write-protected. Note that * already write-protected ptes are not counted. */ unsigned long wp_shared_mapping_range(struct address_space *mapping, pgoff_t first_index, pgoff_t nr) { struct wp_walk wpwalk = { .total = 0 }; i_mmap_lock_read(mapping); WARN_ON(walk_page_mapping(mapping, first_index, nr, &wp_walk_ops, &wpwalk)); i_mmap_unlock_read(mapping); return wpwalk.total; } EXPORT_SYMBOL_GPL(wp_shared_mapping_range); /** * clean_record_shared_mapping_range - Clean and record all ptes in an * address space range * @mapping: The address_space we want to clean * @first_index: The first page offset in the range * @nr: Number of incremental page offsets to cover * @bitmap_pgoff: The page offset of the first bit in @bitmap * @bitmap: Pointer to a bitmap of at least @nr bits. The bitmap needs to * cover the whole range @first_index..@first_index + @nr. * @start: Pointer to number of the first set bit in @bitmap. * is modified as new bits are set by the function. * @end: Pointer to the number of the last set bit in @bitmap. * none set. The value is modified as new bits are set by the function. * * Note: When this function returns there is no guarantee that a CPU has * not already dirtied new ptes. However it will not clean any ptes not * reported in the bitmap. The guarantees are as follows: * a) All ptes dirty when the function starts executing will end up recorded * in the bitmap. * b) All ptes dirtied after that will either remain dirty, be recorded in the * bitmap or both. * * If a caller needs to make sure all dirty ptes are picked up and none * additional are added, it first needs to write-protect the address-space * range and make sure new writers are blocked in page_mkwrite() or * pfn_mkwrite(). And then after a TLB flush following the write-protection * pick up all dirty bits. * * Note: This function currently skips transhuge page-table entries, since * it's intended for dirty-tracking on the PTE level. It will warn on * encountering transhuge dirty entries, though, and can easily be extended * to handle them as well. * * Return: The number of dirty ptes actually cleaned. */ unsigned long clean_record_shared_mapping_range(struct address_space *mapping, pgoff_t first_index, pgoff_t nr, pgoff_t bitmap_pgoff, unsigned long *bitmap, pgoff_t *start, pgoff_t *end) { bool none_set = (*start >= *end); struct clean_walk cwalk = { .base = { .total = 0 }, .bitmap_pgoff = bitmap_pgoff, .bitmap = bitmap, .start = none_set ? nr : *start, .end = none_set ? 0 : *end, }; i_mmap_lock_read(mapping); WARN_ON(walk_page_mapping(mapping, first_index, nr, &clean_walk_ops, &cwalk.base)); i_mmap_unlock_read(mapping); *start = cwalk.start; *end = cwalk.end; return cwalk.base.total; } EXPORT_SYMBOL_GPL(clean_record_shared_mapping_range); |