MIPS: SMP: Clear ASID without confusing has_valid_asid()
authorJames Hogan <james.hogan@imgtec.com>
Wed, 13 Jul 2016 13:12:44 +0000 (14:12 +0100)
committerRalf Baechle <ralf@linux-mips.org>
Fri, 29 Jul 2016 08:19:28 +0000 (10:19 +0200)
The SMP flush_tlb_*() functions may clear the memory map's ASIDs for
other CPUs if the mm has only a single user (the current CPU) in order
to avoid SMP calls. However this makes it appear to has_valid_asid(),
which is used by various cache flush functions, as if the CPUs have
never run in the mm, and therefore can't have cached any of its memory.

For flush_tlb_mm() this doesn't sound unreasonable.

flush_tlb_range() corresponds to flush_cache_range() which does do full
indexed cache flushes, but only on the icache if the specified mapping
is executable, otherwise it doesn't guarantee that there are no cache
contents left for the mm.

flush_tlb_page() corresponds to flush_cache_page(), which will perform
address based cache ops on the specified page only, and also only
touches the icache if the page is executable. It does not guarantee that
there are no cache contents left for the mm.

For example, this affects flush_cache_range() which uses the
has_valid_asid() optimisation. It is required to flush the icache when
mappings are made executable (e.g. using mprotect) so they are
immediately usable. If some code is changed to non executable in order
to be modified then it will not be flushed from the icache during that
time, but the ASID on other CPUs may still be cleared for TLB flushing.
When the code is changed back to executable, flush_cache_range() will
assume the code hasn't run on those other CPUs due to the zero ASID, and
won't invalidate the icache on them.

This is fixed by clearing the other CPUs ASIDs to 1 instead of 0 for the
above two flush_tlb_*() functions when the corresponding cache flushes
are likely to be incomplete (non executable range flush, or any page
flush). This ASID appears valid to has_valid_asid(), but still triggers
ASID regeneration due to the upper ASID version bits being 0, which is
less than the minimum ASID version of 1 and so always treated as stale.

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Paul Burton <paul.burton@imgtec.com>
Cc: Leonid Yegoshin <leonid.yegoshin@imgtec.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/13795/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/kernel/smp.c
arch/mips/mm/c-r4k.c

index f9d01e953acb8b8c2042d932b4a2159d09769350..0c98b4a313be11bdc786cdebf21f035a912a3573 100644 (file)
@@ -512,10 +512,17 @@ void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned l
                smp_on_other_tlbs(flush_tlb_range_ipi, &fd);
        } else {
                unsigned int cpu;
+               int exec = vma->vm_flags & VM_EXEC;
 
                for_each_online_cpu(cpu) {
+                       /*
+                        * flush_cache_range() will only fully flush icache if
+                        * the VMA is executable, otherwise we must invalidate
+                        * ASID without it appearing to has_valid_asid() as if
+                        * mm has been completely unused by that CPU.
+                        */
                        if (cpu != smp_processor_id() && cpu_context(cpu, mm))
-                               cpu_context(cpu, mm) = 0;
+                               cpu_context(cpu, mm) = !exec;
                }
        }
        local_flush_tlb_range(vma, start, end);
@@ -560,8 +567,14 @@ void flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
                unsigned int cpu;
 
                for_each_online_cpu(cpu) {
+                       /*
+                        * flush_cache_page() only does partial flushes, so
+                        * invalidate ASID without it appearing to
+                        * has_valid_asid() as if mm has been completely unused
+                        * by that CPU.
+                        */
                        if (cpu != smp_processor_id() && cpu_context(cpu, vma->vm_mm))
-                               cpu_context(cpu, vma->vm_mm) = 0;
+                               cpu_context(cpu, vma->vm_mm) = 1;
                }
        }
        local_flush_tlb_page(vma, page);
index 645c69c95c9ce83a629f80bd046fee6c570acbb2..9204d4e4f02f56a63797f00c205b3d80fd698916 100644 (file)
@@ -484,6 +484,10 @@ static void r4k__flush_cache_vunmap(void)
        r4k_blast_dcache();
 }
 
+/*
+ * Note: flush_tlb_range() assumes flush_cache_range() sufficiently flushes
+ * whole caches when vma is executable.
+ */
 static inline void local_r4k_flush_cache_range(void * args)
 {
        struct vm_area_struct *vma = args;
This page took 0.027695 seconds and 5 git commands to generate.