root/arch/mips/mm/tlb-r3k.c
// SPDX-License-Identifier: GPL-2.0
/*
 * r2300.c: R2000 and R3000 specific mmu/cache code.
 *
 * Copyright (C) 1996 David S. Miller (davem@davemloft.net)
 *
 * with a lot of changes to make this thing work for R3000s
 * Tx39XX R4k style caches added. HK
 * Copyright (C) 1998, 1999, 2000 Harald Koerfgen
 * Copyright (C) 1998 Gleb Raiko & Vladimir Roganov
 * Copyright (C) 2002  Ralf Baechle
 * Copyright (C) 2002  Maciej W. Rozycki
 */
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/mm.h>

#include <asm/page.h>
#include <asm/mmu_context.h>
#include <asm/tlbmisc.h>
#include <asm/isadep.h>
#include <asm/io.h>
#include <asm/bootinfo.h>
#include <asm/cpu.h>
#include <asm/setup.h>
#include <asm/tlbex.h>

#undef DEBUG_TLB

/* CP0 hazard avoidance. */
#define BARRIER                         \
        __asm__ __volatile__(           \
                ".set   push\n\t"       \
                ".set   noreorder\n\t"  \
                "nop\n\t"               \
                ".set   pop\n\t")

/* TLB operations. */
static void local_flush_tlb_from(int entry)
{
        unsigned long old_ctx;

        old_ctx = read_c0_entryhi() & cpu_asid_mask(&current_cpu_data);
        write_c0_entrylo0(0);
        while (entry < current_cpu_data.tlbsize) {
                write_c0_index(entry << 8);
                write_c0_entryhi((entry | 0x80000) << 12);
                entry++;                                /* BARRIER */
                tlb_write_indexed();
        }
        write_c0_entryhi(old_ctx);
}

void local_flush_tlb_all(void)
{
        unsigned long flags;

#ifdef DEBUG_TLB
        printk("[tlball]");
#endif
        local_irq_save(flags);
        local_flush_tlb_from(8);
        local_irq_restore(flags);
}

void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
                           unsigned long end)
{
        unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
        struct mm_struct *mm = vma->vm_mm;
        int cpu = smp_processor_id();

        if (cpu_context(cpu, mm) != 0) {
                unsigned long size, flags;

#ifdef DEBUG_TLB
                printk("[tlbrange<%lu,0x%08lx,0x%08lx>]",
                        cpu_context(cpu, mm) & asid_mask, start, end);
#endif
                local_irq_save(flags);
                size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
                if (size <= current_cpu_data.tlbsize) {
                        int oldpid = read_c0_entryhi() & asid_mask;
                        int newpid = cpu_context(cpu, mm) & asid_mask;

                        start &= PAGE_MASK;
                        end += PAGE_SIZE - 1;
                        end &= PAGE_MASK;
                        while (start < end) {
                                int idx;

                                write_c0_entryhi(start | newpid);
                                start += PAGE_SIZE;     /* BARRIER */
                                tlb_probe();
                                idx = read_c0_index();
                                write_c0_entrylo0(0);
                                write_c0_entryhi(KSEG0);
                                if (idx < 0)            /* BARRIER */
                                        continue;
                                tlb_write_indexed();
                        }
                        write_c0_entryhi(oldpid);
                } else {
                        drop_mmu_context(mm);
                }
                local_irq_restore(flags);
        }
}

void local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
        unsigned long size, flags;

#ifdef DEBUG_TLB
        printk("[tlbrange<%lu,0x%08lx,0x%08lx>]", start, end);
#endif
        local_irq_save(flags);
        size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
        if (size <= current_cpu_data.tlbsize) {
                int pid = read_c0_entryhi();

                start &= PAGE_MASK;
                end += PAGE_SIZE - 1;
                end &= PAGE_MASK;

                while (start < end) {
                        int idx;

                        write_c0_entryhi(start);
                        start += PAGE_SIZE;             /* BARRIER */
                        tlb_probe();
                        idx = read_c0_index();
                        write_c0_entrylo0(0);
                        write_c0_entryhi(KSEG0);
                        if (idx < 0)                    /* BARRIER */
                                continue;
                        tlb_write_indexed();
                }
                write_c0_entryhi(pid);
        } else {
                local_flush_tlb_all();
        }
        local_irq_restore(flags);
}

void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
{
        unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
        int cpu = smp_processor_id();

        if (cpu_context(cpu, vma->vm_mm) != 0) {
                unsigned long flags;
                int oldpid, newpid, idx;

#ifdef DEBUG_TLB
                printk("[tlbpage<%lu,0x%08lx>]", cpu_context(cpu, vma->vm_mm), page);
#endif
                newpid = cpu_context(cpu, vma->vm_mm) & asid_mask;
                page &= PAGE_MASK;
                local_irq_save(flags);
                oldpid = read_c0_entryhi() & asid_mask;
                write_c0_entryhi(page | newpid);
                BARRIER;
                tlb_probe();
                idx = read_c0_index();
                write_c0_entrylo0(0);
                write_c0_entryhi(KSEG0);
                if (idx < 0)                            /* BARRIER */
                        goto finish;
                tlb_write_indexed();

finish:
                write_c0_entryhi(oldpid);
                local_irq_restore(flags);
        }
}

void __update_tlb(struct vm_area_struct *vma, unsigned long address, pte_t pte)
{
        unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
        unsigned long flags;
        int idx, pid;

        /*
         * Handle debugger faulting in for debuggee.
         */
        if (current->active_mm != vma->vm_mm)
                return;

        pid = read_c0_entryhi() & asid_mask;

#ifdef DEBUG_TLB
        if ((pid != (cpu_context(cpu, vma->vm_mm) & asid_mask)) || (cpu_context(cpu, vma->vm_mm) == 0)) {
                printk("update_mmu_cache: Wheee, bogus tlbpid mmpid=%lu tlbpid=%d\n",
                       (cpu_context(cpu, vma->vm_mm)), pid);
        }
#endif

        local_irq_save(flags);
        address &= PAGE_MASK;
        write_c0_entryhi(address | pid);
        BARRIER;
        tlb_probe();
        idx = read_c0_index();
        write_c0_entrylo0(pte_val(pte));
        write_c0_entryhi(address | pid);
        if (idx < 0) {                                  /* BARRIER */
                tlb_write_random();
        } else {
                tlb_write_indexed();
        }
        write_c0_entryhi(pid);
        local_irq_restore(flags);
}

void add_wired_entry(unsigned long entrylo0, unsigned long entrylo1,
                     unsigned long entryhi, unsigned long pagemask)
{
        unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
        unsigned long flags;
        unsigned long old_ctx;
        static unsigned long wired = 0;

        if (wired < 8) {
#ifdef DEBUG_TLB
                printk("[tlbwired<entry lo0 %8x, hi %8x\n>]\n",
                       entrylo0, entryhi);
#endif

                local_irq_save(flags);
                old_ctx = read_c0_entryhi() & asid_mask;
                write_c0_entrylo0(entrylo0);
                write_c0_entryhi(entryhi);
                write_c0_index(wired);
                wired++;                                /* BARRIER */
                tlb_write_indexed();
                write_c0_entryhi(old_ctx);
                local_flush_tlb_all();
                local_irq_restore(flags);
        }
}

void tlb_init(void)
{
        local_flush_tlb_from(0);
        build_tlb_refill_handler();
}