#include <linux/sched.h>
 #include <linux/sched/mm.h>
 #include <linux/syscalls.h>
+#include <linux/vmalloc.h>
 #include <asm/pdc.h>
 #include <asm/cache.h>
 #include <asm/cacheflush.h>
 #include <asm/mmu_context.h>
 #include <asm/cachectl.h>
 
+#define PTR_PAGE_ALIGN_DOWN(addr) PTR_ALIGN_DOWN(addr, PAGE_SIZE)
+
+/*
+ * When nonzero, use _PAGE_ACCESSED bit to try to reduce the number
+ * of page flushes done flush_cache_page_if_present. There are some
+ * pros and cons in using this option. It may increase the risk of
+ * random segmentation faults.
+ */
+#define CONFIG_FLUSH_PAGE_ACCESSED     0
+
 int split_tlb __ro_after_init;
 int dcache_stride __ro_after_init;
 int icache_stride __ro_after_init;
 EXPORT_SYMBOL(dcache_stride);
 
+/* Internal implementation in arch/parisc/kernel/pacache.S */
 void flush_dcache_page_asm(unsigned long phys_addr, unsigned long vaddr);
 EXPORT_SYMBOL(flush_dcache_page_asm);
 void purge_dcache_page_asm(unsigned long phys_addr, unsigned long vaddr);
 void flush_icache_page_asm(unsigned long phys_addr, unsigned long vaddr);
-
-/* Internal implementation in arch/parisc/kernel/pacache.S */
 void flush_data_cache_local(void *);  /* flushes local data-cache only */
 void flush_instruction_cache_local(void); /* flushes local code-cache only */
 
+static void flush_kernel_dcache_page_addr(const void *addr);
+
 /* On some machines (i.e., ones with the Merced bus), there can be
  * only a single PxTLB broadcast at a time; this must be guaranteed
  * by software. We need a spinlock around all TLB flushes to ensure
 {
        if (!static_branch_likely(&parisc_has_cache))
                return;
+
+       /*
+        * The TLB is the engine of coherence on parisc.  The CPU is
+        * entitled to speculate any page with a TLB mapping, so here
+        * we kill the mapping then flush the page along a special flush
+        * only alias mapping. This guarantees that the page is no-longer
+        * in the cache for any process and nor may it be speculatively
+        * read in (until the user or kernel specifically accesses it,
+        * of course).
+        */
+       flush_tlb_page(vma, vmaddr);
+
        preempt_disable();
        flush_dcache_page_asm(physaddr, vmaddr);
        if (vma->vm_flags & VM_EXEC)
        preempt_enable();
 }
 
-static void flush_user_cache_page(struct vm_area_struct *vma, unsigned long vmaddr)
+static void flush_kernel_dcache_page_addr(const void *addr)
 {
-       unsigned long flags, space, pgd, prot;
-#ifdef CONFIG_TLB_PTLOCK
-       unsigned long pgd_lock;
-#endif
+       unsigned long vaddr = (unsigned long)addr;
+       unsigned long flags;
 
-       vmaddr &= PAGE_MASK;
+       /* Purge TLB entry to remove translation on all CPUs */
+       purge_tlb_start(flags);
+       pdtlb(SR_KERNEL, addr);
+       purge_tlb_end(flags);
 
+       /* Use tmpalias flush to prevent data cache move-in */
        preempt_disable();
+       flush_dcache_page_asm(__pa(vaddr), vaddr);
+       preempt_enable();
+}
 
-       /* Set context for flush */
-       local_irq_save(flags);
-       prot = mfctl(8);
-       space = mfsp(SR_USER);
-       pgd = mfctl(25);
-#ifdef CONFIG_TLB_PTLOCK
-       pgd_lock = mfctl(28);
-#endif
-       switch_mm_irqs_off(NULL, vma->vm_mm, NULL);
-       local_irq_restore(flags);
-
-       flush_user_dcache_range_asm(vmaddr, vmaddr + PAGE_SIZE);
-       if (vma->vm_flags & VM_EXEC)
-               flush_user_icache_range_asm(vmaddr, vmaddr + PAGE_SIZE);
-       flush_tlb_page(vma, vmaddr);
+static void flush_kernel_icache_page_addr(const void *addr)
+{
+       unsigned long vaddr = (unsigned long)addr;
+       unsigned long flags;
 
-       /* Restore previous context */
-       local_irq_save(flags);
-#ifdef CONFIG_TLB_PTLOCK
-       mtctl(pgd_lock, 28);
-#endif
-       mtctl(pgd, 25);
-       mtsp(space, SR_USER);
-       mtctl(prot, 8);
-       local_irq_restore(flags);
+       /* Purge TLB entry to remove translation on all CPUs */
+       purge_tlb_start(flags);
+       pdtlb(SR_KERNEL, addr);
+       purge_tlb_end(flags);
 
+       /* Use tmpalias flush to prevent instruction cache move-in */
+       preempt_disable();
+       flush_icache_page_asm(__pa(vaddr), vaddr);
        preempt_enable();
 }
 
+void kunmap_flush_on_unmap(const void *addr)
+{
+       flush_kernel_dcache_page_addr(addr);
+}
+EXPORT_SYMBOL(kunmap_flush_on_unmap);
+
 void flush_icache_pages(struct vm_area_struct *vma, struct page *page,
                unsigned int nr)
 {
 
        for (;;) {
                flush_kernel_dcache_page_addr(kaddr);
-               flush_kernel_icache_page(kaddr);
+               flush_kernel_icache_page_addr(kaddr);
                if (--nr == 0)
                        break;
                kaddr += PAGE_SIZE;
        }
 }
 
+/*
+ * Walk page directory for MM to find PTEP pointer for address ADDR.
+ */
 static inline pte_t *get_ptep(struct mm_struct *mm, unsigned long addr)
 {
        pte_t *ptep = NULL;
                == (_PAGE_PRESENT | _PAGE_ACCESSED);
 }
 
+/*
+ * Return user physical address. Returns 0 if page is not present.
+ */
+static inline unsigned long get_upa(struct mm_struct *mm, unsigned long addr)
+{
+       unsigned long flags, space, pgd, prot, pa;
+#ifdef CONFIG_TLB_PTLOCK
+       unsigned long pgd_lock;
+#endif
+
+       /* Save context */
+       local_irq_save(flags);
+       prot = mfctl(8);
+       space = mfsp(SR_USER);
+       pgd = mfctl(25);
+#ifdef CONFIG_TLB_PTLOCK
+       pgd_lock = mfctl(28);
+#endif
+
+       /* Set context for lpa_user */
+       switch_mm_irqs_off(NULL, mm, NULL);
+       pa = lpa_user(addr);
+
+       /* Restore previous context */
+#ifdef CONFIG_TLB_PTLOCK
+       mtctl(pgd_lock, 28);
+#endif
+       mtctl(pgd, 25);
+       mtsp(space, SR_USER);
+       mtctl(prot, 8);
+       local_irq_restore(flags);
+
+       return pa;
+}
+
 void flush_dcache_folio(struct folio *folio)
 {
        struct address_space *mapping = folio_flush_mapping(folio);
                if (addr + nr * PAGE_SIZE > vma->vm_end)
                        nr = (vma->vm_end - addr) / PAGE_SIZE;
 
-               if (parisc_requires_coherency()) {
-                       for (i = 0; i < nr; i++) {
-                               pte_t *ptep = get_ptep(vma->vm_mm,
-                                                       addr + i * PAGE_SIZE);
-                               if (!ptep)
-                                       continue;
-                               if (pte_needs_flush(*ptep))
-                                       flush_user_cache_page(vma,
-                                                       addr + i * PAGE_SIZE);
-                               /* Optimise accesses to the same table? */
-                               pte_unmap(ptep);
-                       }
-               } else {
+               if (old_addr == 0 || (old_addr & (SHM_COLOUR - 1))
+                                       != (addr & (SHM_COLOUR - 1))) {
+                       for (i = 0; i < nr; i++)
+                               __flush_cache_page(vma,
+                                       addr + i * PAGE_SIZE,
+                                       (pfn + i) * PAGE_SIZE);
                        /*
-                        * The TLB is the engine of coherence on parisc:
-                        * The CPU is entitled to speculate any page
-                        * with a TLB mapping, so here we kill the
-                        * mapping then flush the page along a special
-                        * flush only alias mapping. This guarantees that
-                        * the page is no-longer in the cache for any
-                        * process and nor may it be speculatively read
-                        * in (until the user or kernel specifically
-                        * accesses it, of course)
+                        * Software is allowed to have any number
+                        * of private mappings to a page.
                         */
-                       for (i = 0; i < nr; i++)
-                               flush_tlb_page(vma, addr + i * PAGE_SIZE);
-                       if (old_addr == 0 || (old_addr & (SHM_COLOUR - 1))
-                                       != (addr & (SHM_COLOUR - 1))) {
-                               for (i = 0; i < nr; i++)
-                                       __flush_cache_page(vma,
-                                               addr + i * PAGE_SIZE,
-                                               (pfn + i) * PAGE_SIZE);
-                               /*
-                                * Software is allowed to have any number
-                                * of private mappings to a page.
-                                */
-                               if (!(vma->vm_flags & VM_SHARED))
-                                       continue;
-                               if (old_addr)
-                                       pr_err("INEQUIVALENT ALIASES 0x%lx and 0x%lx in file %pD\n",
-                                               old_addr, addr, vma->vm_file);
-                               if (nr == folio_nr_pages(folio))
-                                       old_addr = addr;
-                       }
+                       if (!(vma->vm_flags & VM_SHARED))
+                               continue;
+                       if (old_addr)
+                               pr_err("INEQUIVALENT ALIASES 0x%lx and 0x%lx in file %pD\n",
+                                       old_addr, addr, vma->vm_file);
+                       if (nr == folio_nr_pages(folio))
+                               old_addr = addr;
                }
                WARN_ON(++count == 4096);
        }
 extern void clear_user_page_asm(void *, unsigned long);
 extern void copy_user_page_asm(void *, void *, unsigned long);
 
-void flush_kernel_dcache_page_addr(const void *addr)
-{
-       unsigned long flags;
-
-       flush_kernel_dcache_page_asm(addr);
-       purge_tlb_start(flags);
-       pdtlb(SR_KERNEL, addr);
-       purge_tlb_end(flags);
-}
-EXPORT_SYMBOL(flush_kernel_dcache_page_addr);
-
 static void flush_cache_page_if_present(struct vm_area_struct *vma,
-       unsigned long vmaddr, unsigned long pfn)
+       unsigned long vmaddr)
 {
+#if CONFIG_FLUSH_PAGE_ACCESSED
        bool needs_flush = false;
-       pte_t *ptep;
+       pte_t *ptep, pte;
 
-       /*
-        * The pte check is racy and sometimes the flush will trigger
-        * a non-access TLB miss. Hopefully, the page has already been
-        * flushed.
-        */
        ptep = get_ptep(vma->vm_mm, vmaddr);
        if (ptep) {
-               needs_flush = pte_needs_flush(*ptep);
+               pte = ptep_get(ptep);
+               needs_flush = pte_needs_flush(pte);
                pte_unmap(ptep);
        }
        if (needs_flush)
-               flush_cache_page(vma, vmaddr, pfn);
+               __flush_cache_page(vma, vmaddr, PFN_PHYS(pte_pfn(pte)));
+#else
+       struct mm_struct *mm = vma->vm_mm;
+       unsigned long physaddr = get_upa(mm, vmaddr);
+
+       if (physaddr)
+               __flush_cache_page(vma, vmaddr, PAGE_ALIGN_DOWN(physaddr));
+#endif
 }
 
 void copy_user_highpage(struct page *to, struct page *from,
 
        kfrom = kmap_local_page(from);
        kto = kmap_local_page(to);
-       flush_cache_page_if_present(vma, vaddr, page_to_pfn(from));
+       __flush_cache_page(vma, vaddr, PFN_PHYS(page_to_pfn(from)));
        copy_page_asm(kto, kfrom);
        kunmap_local(kto);
        kunmap_local(kfrom);
 void copy_to_user_page(struct vm_area_struct *vma, struct page *page,
                unsigned long user_vaddr, void *dst, void *src, int len)
 {
-       flush_cache_page_if_present(vma, user_vaddr, page_to_pfn(page));
+       __flush_cache_page(vma, user_vaddr, PFN_PHYS(page_to_pfn(page)));
        memcpy(dst, src, len);
-       flush_kernel_dcache_range_asm((unsigned long)dst, (unsigned long)dst + len);
+       flush_kernel_dcache_page_addr(PTR_PAGE_ALIGN_DOWN(dst));
 }
 
 void copy_from_user_page(struct vm_area_struct *vma, struct page *page,
                unsigned long user_vaddr, void *dst, void *src, int len)
 {
-       flush_cache_page_if_present(vma, user_vaddr, page_to_pfn(page));
+       __flush_cache_page(vma, user_vaddr, PFN_PHYS(page_to_pfn(page)));
        memcpy(dst, src, len);
+       flush_kernel_dcache_page_addr(PTR_PAGE_ALIGN_DOWN(src));
 }
 
 /* __flush_tlb_range()
 
 static void flush_cache_pages(struct vm_area_struct *vma, unsigned long start, unsigned long end)
 {
-       unsigned long addr, pfn;
-       pte_t *ptep;
-
-       for (addr = start; addr < end; addr += PAGE_SIZE) {
-               bool needs_flush = false;
-               /*
-                * The vma can contain pages that aren't present. Although
-                * the pte search is expensive, we need the pte to find the
-                * page pfn and to check whether the page should be flushed.
-                */
-               ptep = get_ptep(vma->vm_mm, addr);
-               if (ptep) {
-                       needs_flush = pte_needs_flush(*ptep);
-                       pfn = pte_pfn(*ptep);
-                       pte_unmap(ptep);
-               }
-               if (needs_flush) {
-                       if (parisc_requires_coherency()) {
-                               flush_user_cache_page(vma, addr);
-                       } else {
-                               if (WARN_ON(!pfn_valid(pfn)))
-                                       return;
-                               __flush_cache_page(vma, addr, PFN_PHYS(pfn));
-                       }
-               }
-       }
+       unsigned long addr;
+
+       for (addr = start; addr < end; addr += PAGE_SIZE)
+               flush_cache_page_if_present(vma, addr);
 }
 
 static inline unsigned long mm_total_size(struct mm_struct *mm)
                if (WARN_ON(IS_ENABLED(CONFIG_SMP) && arch_irqs_disabled()))
                        return;
                flush_tlb_range(vma, start, end);
-               flush_cache_all();
+               if (vma->vm_flags & VM_EXEC)
+                       flush_cache_all();
+               else
+                       flush_data_cache();
                return;
        }
 
-       flush_cache_pages(vma, start, end);
+       flush_cache_pages(vma, start & PAGE_MASK, end);
 }
 
 void flush_cache_page(struct vm_area_struct *vma, unsigned long vmaddr, unsigned long pfn)
 {
-       if (WARN_ON(!pfn_valid(pfn)))
-               return;
-       if (parisc_requires_coherency())
-               flush_user_cache_page(vma, vmaddr);
-       else
-               __flush_cache_page(vma, vmaddr, PFN_PHYS(pfn));
+       __flush_cache_page(vma, vmaddr, PFN_PHYS(pfn));
 }
 
 void flush_anon_page(struct vm_area_struct *vma, struct page *page, unsigned long vmaddr)
        if (!PageAnon(page))
                return;
 
-       if (parisc_requires_coherency()) {
-               if (vma->vm_flags & VM_SHARED)
-                       flush_data_cache();
-               else
-                       flush_user_cache_page(vma, vmaddr);
+       __flush_cache_page(vma, vmaddr, PFN_PHYS(page_to_pfn(page)));
+}
+
+int ptep_clear_flush_young(struct vm_area_struct *vma, unsigned long addr,
+                          pte_t *ptep)
+{
+       pte_t pte = ptep_get(ptep);
+
+       if (!pte_young(pte))
+               return 0;
+       set_pte(ptep, pte_mkold(pte));
+#if CONFIG_FLUSH_PAGE_ACCESSED
+       __flush_cache_page(vma, addr, PFN_PHYS(pte_pfn(pte)));
+#endif
+       return 1;
+}
+
+/*
+ * After a PTE is cleared, we have no way to flush the cache for
+ * the physical page. On PA8800 and PA8900 processors, these lines
+ * can cause random cache corruption. Thus, we must flush the cache
+ * as well as the TLB when clearing a PTE that's valid.
+ */
+pte_t ptep_clear_flush(struct vm_area_struct *vma, unsigned long addr,
+                      pte_t *ptep)
+{
+       struct mm_struct *mm = (vma)->vm_mm;
+       pte_t pte = ptep_get_and_clear(mm, addr, ptep);
+       unsigned long pfn = pte_pfn(pte);
+
+       if (pfn_valid(pfn))
+               __flush_cache_page(vma, addr, PFN_PHYS(pfn));
+       else if (pte_accessible(mm, pte))
+               flush_tlb_page(vma, addr);
+
+       return pte;
+}
+
+/*
+ * The physical address for pages in the ioremap case can be obtained
+ * from the vm_struct struct. I wasn't able to successfully handle the
+ * vmalloc and vmap cases. We have an array of struct page pointers in
+ * the uninitialized vmalloc case but the flush failed using page_to_pfn.
+ */
+void flush_cache_vmap(unsigned long start, unsigned long end)
+{
+       unsigned long addr, physaddr;
+       struct vm_struct *vm;
+
+       /* Prevent cache move-in */
+       flush_tlb_kernel_range(start, end);
+
+       if (end - start >= parisc_cache_flush_threshold) {
+               flush_cache_all();
                return;
        }
 
-       flush_tlb_page(vma, vmaddr);
-       preempt_disable();
-       flush_dcache_page_asm(page_to_phys(page), vmaddr);
-       preempt_enable();
+       if (WARN_ON_ONCE(!is_vmalloc_addr((void *)start))) {
+               flush_cache_all();
+               return;
+       }
+
+       vm = find_vm_area((void *)start);
+       if (WARN_ON_ONCE(!vm)) {
+               flush_cache_all();
+               return;
+       }
+
+       /* The physical addresses of IOREMAP regions are contiguous */
+       if (vm->flags & VM_IOREMAP) {
+               physaddr = vm->phys_addr;
+               for (addr = start; addr < end; addr += PAGE_SIZE) {
+                       preempt_disable();
+                       flush_dcache_page_asm(physaddr, start);
+                       flush_icache_page_asm(physaddr, start);
+                       preempt_enable();
+                       physaddr += PAGE_SIZE;
+               }
+               return;
+       }
+
+       flush_cache_all();
 }
+EXPORT_SYMBOL(flush_cache_vmap);
 
+/*
+ * The vm_struct has been retired and the page table is set up. The
+ * last page in the range is a guard page. Its physical address can't
+ * be determined using lpa, so there is no way to flush the range
+ * using flush_dcache_page_asm.
+ */
+void flush_cache_vunmap(unsigned long start, unsigned long end)
+{
+       /* Prevent cache move-in */
+       flush_tlb_kernel_range(start, end);
+       flush_data_cache();
+}
+EXPORT_SYMBOL(flush_cache_vunmap);
+
+/*
+ * On systems with PA8800/PA8900 processors, there is no way to flush
+ * a vmap range other than using the architected loop to flush the
+ * entire cache. The page directory is not set up, so we can't use
+ * fdc, etc. FDCE/FICE don't work to flush a portion of the cache.
+ * L2 is physically indexed but FDCE/FICE instructions in virtual
+ * mode output their virtual address on the core bus, not their
+ * real address. As a result, the L2 cache index formed from the
+ * virtual address will most likely not be the same as the L2 index
+ * formed from the real address.
+ */
 void flush_kernel_vmap_range(void *vaddr, int size)
 {
        unsigned long start = (unsigned long)vaddr;
        unsigned long end = start + size;
 
-       if ((!IS_ENABLED(CONFIG_SMP) || !arch_irqs_disabled()) &&
-           (unsigned long)size >= parisc_cache_flush_threshold) {
-               flush_tlb_kernel_range(start, end);
-               flush_data_cache();
+       flush_tlb_kernel_range(start, end);
+
+       if (!static_branch_likely(&parisc_has_dcache))
+               return;
+
+       /* If interrupts are disabled, we can only do local flush */
+       if (WARN_ON(IS_ENABLED(CONFIG_SMP) && arch_irqs_disabled())) {
+               flush_data_cache_local(NULL);
                return;
        }
 
-       flush_kernel_dcache_range_asm(start, end);
-       flush_tlb_kernel_range(start, end);
+       flush_data_cache();
 }
 EXPORT_SYMBOL(flush_kernel_vmap_range);
 
        /* Ensure DMA is complete */
        asm_syncdma();
 
-       if ((!IS_ENABLED(CONFIG_SMP) || !arch_irqs_disabled()) &&
-           (unsigned long)size >= parisc_cache_flush_threshold) {
-               flush_tlb_kernel_range(start, end);
-               flush_data_cache();
+       flush_tlb_kernel_range(start, end);
+
+       if (!static_branch_likely(&parisc_has_dcache))
+               return;
+
+       /* If interrupts are disabled, we can only do local flush */
+       if (WARN_ON(IS_ENABLED(CONFIG_SMP) && arch_irqs_disabled())) {
+               flush_data_cache_local(NULL);
                return;
        }
 
-       purge_kernel_dcache_range_asm(start, end);
-       flush_tlb_kernel_range(start, end);
+       flush_data_cache();
 }
 EXPORT_SYMBOL(invalidate_kernel_vmap_range);