162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * r2300.c: R2000 and R3000 specific mmu/cache code.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 1996 David S. Miller (davem@davemloft.net)
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * with a lot of changes to make this thing work for R3000s
862306a36Sopenharmony_ci * Tx39XX R4k style caches added. HK
962306a36Sopenharmony_ci * Copyright (C) 1998, 1999, 2000 Harald Koerfgen
1062306a36Sopenharmony_ci * Copyright (C) 1998 Gleb Raiko & Vladimir Roganov
1162306a36Sopenharmony_ci * Copyright (C) 2002  Ralf Baechle
1262306a36Sopenharmony_ci * Copyright (C) 2002  Maciej W. Rozycki
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/sched.h>
1662306a36Sopenharmony_ci#include <linux/smp.h>
1762306a36Sopenharmony_ci#include <linux/mm.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <asm/page.h>
2062306a36Sopenharmony_ci#include <asm/mmu_context.h>
2162306a36Sopenharmony_ci#include <asm/tlbmisc.h>
2262306a36Sopenharmony_ci#include <asm/isadep.h>
2362306a36Sopenharmony_ci#include <asm/io.h>
2462306a36Sopenharmony_ci#include <asm/bootinfo.h>
2562306a36Sopenharmony_ci#include <asm/cpu.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#undef DEBUG_TLB
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ciextern void build_tlb_refill_handler(void);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* CP0 hazard avoidance. */
3262306a36Sopenharmony_ci#define BARRIER				\
3362306a36Sopenharmony_ci	__asm__ __volatile__(		\
3462306a36Sopenharmony_ci		".set	push\n\t"	\
3562306a36Sopenharmony_ci		".set	noreorder\n\t"	\
3662306a36Sopenharmony_ci		"nop\n\t"		\
3762306a36Sopenharmony_ci		".set	pop\n\t")
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* TLB operations. */
4062306a36Sopenharmony_cistatic void local_flush_tlb_from(int entry)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	unsigned long old_ctx;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	old_ctx = read_c0_entryhi() & cpu_asid_mask(&current_cpu_data);
4562306a36Sopenharmony_ci	write_c0_entrylo0(0);
4662306a36Sopenharmony_ci	while (entry < current_cpu_data.tlbsize) {
4762306a36Sopenharmony_ci		write_c0_index(entry << 8);
4862306a36Sopenharmony_ci		write_c0_entryhi((entry | 0x80000) << 12);
4962306a36Sopenharmony_ci		entry++;				/* BARRIER */
5062306a36Sopenharmony_ci		tlb_write_indexed();
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci	write_c0_entryhi(old_ctx);
5362306a36Sopenharmony_ci}
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_civoid local_flush_tlb_all(void)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	unsigned long flags;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci#ifdef DEBUG_TLB
6062306a36Sopenharmony_ci	printk("[tlball]");
6162306a36Sopenharmony_ci#endif
6262306a36Sopenharmony_ci	local_irq_save(flags);
6362306a36Sopenharmony_ci	local_flush_tlb_from(8);
6462306a36Sopenharmony_ci	local_irq_restore(flags);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_civoid local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
6862306a36Sopenharmony_ci			   unsigned long end)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
7162306a36Sopenharmony_ci	struct mm_struct *mm = vma->vm_mm;
7262306a36Sopenharmony_ci	int cpu = smp_processor_id();
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	if (cpu_context(cpu, mm) != 0) {
7562306a36Sopenharmony_ci		unsigned long size, flags;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci#ifdef DEBUG_TLB
7862306a36Sopenharmony_ci		printk("[tlbrange<%lu,0x%08lx,0x%08lx>]",
7962306a36Sopenharmony_ci			cpu_context(cpu, mm) & asid_mask, start, end);
8062306a36Sopenharmony_ci#endif
8162306a36Sopenharmony_ci		local_irq_save(flags);
8262306a36Sopenharmony_ci		size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
8362306a36Sopenharmony_ci		if (size <= current_cpu_data.tlbsize) {
8462306a36Sopenharmony_ci			int oldpid = read_c0_entryhi() & asid_mask;
8562306a36Sopenharmony_ci			int newpid = cpu_context(cpu, mm) & asid_mask;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci			start &= PAGE_MASK;
8862306a36Sopenharmony_ci			end += PAGE_SIZE - 1;
8962306a36Sopenharmony_ci			end &= PAGE_MASK;
9062306a36Sopenharmony_ci			while (start < end) {
9162306a36Sopenharmony_ci				int idx;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci				write_c0_entryhi(start | newpid);
9462306a36Sopenharmony_ci				start += PAGE_SIZE;	/* BARRIER */
9562306a36Sopenharmony_ci				tlb_probe();
9662306a36Sopenharmony_ci				idx = read_c0_index();
9762306a36Sopenharmony_ci				write_c0_entrylo0(0);
9862306a36Sopenharmony_ci				write_c0_entryhi(KSEG0);
9962306a36Sopenharmony_ci				if (idx < 0)		/* BARRIER */
10062306a36Sopenharmony_ci					continue;
10162306a36Sopenharmony_ci				tlb_write_indexed();
10262306a36Sopenharmony_ci			}
10362306a36Sopenharmony_ci			write_c0_entryhi(oldpid);
10462306a36Sopenharmony_ci		} else {
10562306a36Sopenharmony_ci			drop_mmu_context(mm);
10662306a36Sopenharmony_ci		}
10762306a36Sopenharmony_ci		local_irq_restore(flags);
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_civoid local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	unsigned long size, flags;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci#ifdef DEBUG_TLB
11662306a36Sopenharmony_ci	printk("[tlbrange<%lu,0x%08lx,0x%08lx>]", start, end);
11762306a36Sopenharmony_ci#endif
11862306a36Sopenharmony_ci	local_irq_save(flags);
11962306a36Sopenharmony_ci	size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
12062306a36Sopenharmony_ci	if (size <= current_cpu_data.tlbsize) {
12162306a36Sopenharmony_ci		int pid = read_c0_entryhi();
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		start &= PAGE_MASK;
12462306a36Sopenharmony_ci		end += PAGE_SIZE - 1;
12562306a36Sopenharmony_ci		end &= PAGE_MASK;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci		while (start < end) {
12862306a36Sopenharmony_ci			int idx;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci			write_c0_entryhi(start);
13162306a36Sopenharmony_ci			start += PAGE_SIZE;		/* BARRIER */
13262306a36Sopenharmony_ci			tlb_probe();
13362306a36Sopenharmony_ci			idx = read_c0_index();
13462306a36Sopenharmony_ci			write_c0_entrylo0(0);
13562306a36Sopenharmony_ci			write_c0_entryhi(KSEG0);
13662306a36Sopenharmony_ci			if (idx < 0)			/* BARRIER */
13762306a36Sopenharmony_ci				continue;
13862306a36Sopenharmony_ci			tlb_write_indexed();
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci		write_c0_entryhi(pid);
14162306a36Sopenharmony_ci	} else {
14262306a36Sopenharmony_ci		local_flush_tlb_all();
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci	local_irq_restore(flags);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_civoid local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
15062306a36Sopenharmony_ci	int cpu = smp_processor_id();
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (cpu_context(cpu, vma->vm_mm) != 0) {
15362306a36Sopenharmony_ci		unsigned long flags;
15462306a36Sopenharmony_ci		int oldpid, newpid, idx;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci#ifdef DEBUG_TLB
15762306a36Sopenharmony_ci		printk("[tlbpage<%lu,0x%08lx>]", cpu_context(cpu, vma->vm_mm), page);
15862306a36Sopenharmony_ci#endif
15962306a36Sopenharmony_ci		newpid = cpu_context(cpu, vma->vm_mm) & asid_mask;
16062306a36Sopenharmony_ci		page &= PAGE_MASK;
16162306a36Sopenharmony_ci		local_irq_save(flags);
16262306a36Sopenharmony_ci		oldpid = read_c0_entryhi() & asid_mask;
16362306a36Sopenharmony_ci		write_c0_entryhi(page | newpid);
16462306a36Sopenharmony_ci		BARRIER;
16562306a36Sopenharmony_ci		tlb_probe();
16662306a36Sopenharmony_ci		idx = read_c0_index();
16762306a36Sopenharmony_ci		write_c0_entrylo0(0);
16862306a36Sopenharmony_ci		write_c0_entryhi(KSEG0);
16962306a36Sopenharmony_ci		if (idx < 0)				/* BARRIER */
17062306a36Sopenharmony_ci			goto finish;
17162306a36Sopenharmony_ci		tlb_write_indexed();
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cifinish:
17462306a36Sopenharmony_ci		write_c0_entryhi(oldpid);
17562306a36Sopenharmony_ci		local_irq_restore(flags);
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_civoid __update_tlb(struct vm_area_struct *vma, unsigned long address, pte_t pte)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
18262306a36Sopenharmony_ci	unsigned long flags;
18362306a36Sopenharmony_ci	int idx, pid;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	/*
18662306a36Sopenharmony_ci	 * Handle debugger faulting in for debugee.
18762306a36Sopenharmony_ci	 */
18862306a36Sopenharmony_ci	if (current->active_mm != vma->vm_mm)
18962306a36Sopenharmony_ci		return;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	pid = read_c0_entryhi() & asid_mask;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci#ifdef DEBUG_TLB
19462306a36Sopenharmony_ci	if ((pid != (cpu_context(cpu, vma->vm_mm) & asid_mask)) || (cpu_context(cpu, vma->vm_mm) == 0)) {
19562306a36Sopenharmony_ci		printk("update_mmu_cache: Wheee, bogus tlbpid mmpid=%lu tlbpid=%d\n",
19662306a36Sopenharmony_ci		       (cpu_context(cpu, vma->vm_mm)), pid);
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci#endif
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	local_irq_save(flags);
20162306a36Sopenharmony_ci	address &= PAGE_MASK;
20262306a36Sopenharmony_ci	write_c0_entryhi(address | pid);
20362306a36Sopenharmony_ci	BARRIER;
20462306a36Sopenharmony_ci	tlb_probe();
20562306a36Sopenharmony_ci	idx = read_c0_index();
20662306a36Sopenharmony_ci	write_c0_entrylo0(pte_val(pte));
20762306a36Sopenharmony_ci	write_c0_entryhi(address | pid);
20862306a36Sopenharmony_ci	if (idx < 0) {					/* BARRIER */
20962306a36Sopenharmony_ci		tlb_write_random();
21062306a36Sopenharmony_ci	} else {
21162306a36Sopenharmony_ci		tlb_write_indexed();
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci	write_c0_entryhi(pid);
21462306a36Sopenharmony_ci	local_irq_restore(flags);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_civoid add_wired_entry(unsigned long entrylo0, unsigned long entrylo1,
21862306a36Sopenharmony_ci		     unsigned long entryhi, unsigned long pagemask)
21962306a36Sopenharmony_ci{
22062306a36Sopenharmony_ci	unsigned long asid_mask = cpu_asid_mask(&current_cpu_data);
22162306a36Sopenharmony_ci	unsigned long flags;
22262306a36Sopenharmony_ci	unsigned long old_ctx;
22362306a36Sopenharmony_ci	static unsigned long wired = 0;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (wired < 8) {
22662306a36Sopenharmony_ci#ifdef DEBUG_TLB
22762306a36Sopenharmony_ci		printk("[tlbwired<entry lo0 %8x, hi %8x\n>]\n",
22862306a36Sopenharmony_ci		       entrylo0, entryhi);
22962306a36Sopenharmony_ci#endif
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		local_irq_save(flags);
23262306a36Sopenharmony_ci		old_ctx = read_c0_entryhi() & asid_mask;
23362306a36Sopenharmony_ci		write_c0_entrylo0(entrylo0);
23462306a36Sopenharmony_ci		write_c0_entryhi(entryhi);
23562306a36Sopenharmony_ci		write_c0_index(wired);
23662306a36Sopenharmony_ci		wired++;				/* BARRIER */
23762306a36Sopenharmony_ci		tlb_write_indexed();
23862306a36Sopenharmony_ci		write_c0_entryhi(old_ctx);
23962306a36Sopenharmony_ci		local_flush_tlb_all();
24062306a36Sopenharmony_ci		local_irq_restore(flags);
24162306a36Sopenharmony_ci	}
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_civoid tlb_init(void)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	local_flush_tlb_from(0);
24762306a36Sopenharmony_ci	build_tlb_refill_handler();
24862306a36Sopenharmony_ci}
249