Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Linux/MIPS-specific parts of the memory manager.
#
obj-y += cache.o
obj-y += context.o
obj-y += extable.o
obj-y += fault.o
obj-y += init.o
obj-y += mmap.o
obj-y += page.o
obj-y += page-funcs.o
obj-y += pgtable.o
obj-y += tlbex.o
obj-y += tlbex-fault.o
obj-y += tlb-funcs.o
ifdef CONFIG_CPU_MICROMIPS
obj-y += uasm-micromips.o
else
obj-y += uasm-mips.o
endif
ifndef CONFIG_EVA
obj-y += maccess.o
endif
obj-$(CONFIG_32BIT) += ioremap.o pgtable-32.o
obj-$(CONFIG_64BIT) += ioremap64.o pgtable-64.o
obj-$(CONFIG_HIGHMEM) += highmem.o
obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o
obj-$(CONFIG_DMA_NONCOHERENT) += dma-noncoherent.o
obj-$(CONFIG_CPU_R3K_TLB) += tlb-r3k.o
obj-$(CONFIG_CPU_R4K_CACHE_TLB) += c-r4k.o cex-gen.o tlb-r4k.o
obj-$(CONFIG_CPU_R3000) += c-r3k.o
obj-$(CONFIG_CPU_SB1) += c-r4k.o cerr-sb1.o cex-sb1.o tlb-r4k.o
obj-$(CONFIG_CPU_CAVIUM_OCTEON) += c-octeon.o cex-oct.o tlb-r4k.o
obj-$(CONFIG_IP22_CPU_SCACHE) += sc-ip22.o
obj-$(CONFIG_R5000_CPU_SCACHE) += sc-r5k.o
obj-$(CONFIG_RM7000_CPU_SCACHE) += sc-rm7k.o
obj-$(CONFIG_MIPS_CPU_SCACHE) += sc-mips.o
obj-$(CONFIG_SCACHE_DEBUGFS) += sc-debugfs.o
obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o
+355
View File
@@ -0,0 +1,355 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2005-2007 Cavium Networks
*/
#include <linux/export.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/mm.h>
#include <linux/bitops.h>
#include <linux/cpu.h>
#include <linux/io.h>
#include <asm/bcache.h>
#include <asm/bootinfo.h>
#include <asm/cacheops.h>
#include <asm/cpu-features.h>
#include <asm/cpu-type.h>
#include <asm/page.h>
#include <asm/r4kcache.h>
#include <asm/traps.h>
#include <asm/mmu_context.h>
#include <asm/octeon/octeon.h>
unsigned long long cache_err_dcache[NR_CPUS];
EXPORT_SYMBOL_GPL(cache_err_dcache);
/*
* Octeon automatically flushes the dcache on tlb changes, so
* from Linux's viewpoint it acts much like a physically
* tagged cache. No flushing is needed
*
*/
static void octeon_flush_data_cache_page(unsigned long addr)
{
/* Nothing to do */
}
static inline void octeon_local_flush_icache(void)
{
asm volatile ("synci 0($0)");
}
/*
* Flush local I-cache for the specified range.
*/
static void local_octeon_flush_icache_range(unsigned long start,
unsigned long end)
{
octeon_local_flush_icache();
}
/**
* octeon_flush_icache_all_cores - Flush caches as necessary for all cores
* affected by a vma. If no vma is supplied, all cores are flushed.
*
* @vma: VMA to flush or NULL to flush all icaches.
*/
static void octeon_flush_icache_all_cores(struct vm_area_struct *vma)
{
extern void octeon_send_ipi_single(int cpu, unsigned int action);
#ifdef CONFIG_SMP
int cpu;
cpumask_t mask;
#endif
mb();
octeon_local_flush_icache();
#ifdef CONFIG_SMP
preempt_disable();
cpu = smp_processor_id();
/*
* If we have a vma structure, we only need to worry about
* cores it has been used on
*/
if (vma)
mask = *mm_cpumask(vma->vm_mm);
else
mask = *cpu_online_mask;
cpumask_clear_cpu(cpu, &mask);
#ifdef CONFIG_CAVIUM_OCTEON_SOC
for_each_cpu(cpu, &mask)
octeon_send_ipi_single(cpu, SMP_ICACHE_FLUSH);
#else
smp_call_function_many(&mask, (smp_call_func_t)octeon_local_flush_icache,
NULL, 1);
#endif
preempt_enable();
#endif
}
/*
* Called to flush the icache on all cores
*/
static void octeon_flush_icache_all(void)
{
octeon_flush_icache_all_cores(NULL);
}
/**
* octeon_flush_cache_mm - flush all memory associated with a memory context.
*
* @mm: Memory context to flush
*/
static void octeon_flush_cache_mm(struct mm_struct *mm)
{
/*
* According to the R4K version of this file, CPUs without
* dcache aliases don't need to do anything here
*/
}
/*
* Flush a range of kernel addresses out of the icache
*
*/
static void octeon_flush_icache_range(unsigned long start, unsigned long end)
{
octeon_flush_icache_all_cores(NULL);
}
/**
* octeon_flush_cache_range - Flush a range out of a vma
*
* @vma: VMA to flush
* @start: beginning address for flush
* @end: ending address for flush
*/
static void octeon_flush_cache_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
if (vma->vm_flags & VM_EXEC)
octeon_flush_icache_all_cores(vma);
}
/**
* octeon_flush_cache_page - Flush a specific page of a vma
*
* @vma: VMA to flush page for
* @page: Page to flush
* @pfn: Page frame number
*/
static void octeon_flush_cache_page(struct vm_area_struct *vma,
unsigned long page, unsigned long pfn)
{
if (vma->vm_flags & VM_EXEC)
octeon_flush_icache_all_cores(vma);
}
static void octeon_flush_kernel_vmap_range(unsigned long vaddr, int size)
{
BUG();
}
/*
* Probe Octeon's caches
*
*/
static void probe_octeon(void)
{
unsigned long icache_size;
unsigned long dcache_size;
unsigned int config1;
struct cpuinfo_mips *c = &current_cpu_data;
int cputype = current_cpu_type();
config1 = read_c0_config1();
switch (cputype) {
case CPU_CAVIUM_OCTEON:
case CPU_CAVIUM_OCTEON_PLUS:
c->icache.linesz = 2 << ((config1 >> 19) & 7);
c->icache.sets = 64 << ((config1 >> 22) & 7);
c->icache.ways = 1 + ((config1 >> 16) & 7);
c->icache.flags |= MIPS_CACHE_VTAG;
icache_size =
c->icache.sets * c->icache.ways * c->icache.linesz;
c->icache.waybit = ffs(icache_size / c->icache.ways) - 1;
c->dcache.linesz = 128;
if (cputype == CPU_CAVIUM_OCTEON_PLUS)
c->dcache.sets = 2; /* CN5XXX has two Dcache sets */
else
c->dcache.sets = 1; /* CN3XXX has one Dcache set */
c->dcache.ways = 64;
dcache_size =
c->dcache.sets * c->dcache.ways * c->dcache.linesz;
c->dcache.waybit = ffs(dcache_size / c->dcache.ways) - 1;
c->options |= MIPS_CPU_PREFETCH;
break;
case CPU_CAVIUM_OCTEON2:
c->icache.linesz = 2 << ((config1 >> 19) & 7);
c->icache.sets = 8;
c->icache.ways = 37;
c->icache.flags |= MIPS_CACHE_VTAG;
icache_size = c->icache.sets * c->icache.ways * c->icache.linesz;
c->dcache.linesz = 128;
c->dcache.ways = 32;
c->dcache.sets = 8;
dcache_size = c->dcache.sets * c->dcache.ways * c->dcache.linesz;
c->options |= MIPS_CPU_PREFETCH;
break;
case CPU_CAVIUM_OCTEON3:
c->icache.linesz = 128;
c->icache.sets = 16;
c->icache.ways = 39;
c->icache.flags |= MIPS_CACHE_VTAG;
icache_size = c->icache.sets * c->icache.ways * c->icache.linesz;
c->dcache.linesz = 128;
c->dcache.ways = 32;
c->dcache.sets = 8;
dcache_size = c->dcache.sets * c->dcache.ways * c->dcache.linesz;
c->options |= MIPS_CPU_PREFETCH;
break;
default:
panic("Unsupported Cavium Networks CPU type");
break;
}
/* compute a couple of other cache variables */
c->icache.waysize = icache_size / c->icache.ways;
c->dcache.waysize = dcache_size / c->dcache.ways;
c->icache.sets = icache_size / (c->icache.linesz * c->icache.ways);
c->dcache.sets = dcache_size / (c->dcache.linesz * c->dcache.ways);
if (smp_processor_id() == 0) {
pr_info("Primary instruction cache %ldkB, %s, %d way, "
"%d sets, linesize %d bytes.\n",
icache_size >> 10,
cpu_has_vtag_icache ?
"virtually tagged" : "physically tagged",
c->icache.ways, c->icache.sets, c->icache.linesz);
pr_info("Primary data cache %ldkB, %d-way, %d sets, "
"linesize %d bytes.\n",
dcache_size >> 10, c->dcache.ways,
c->dcache.sets, c->dcache.linesz);
}
}
static void octeon_cache_error_setup(void)
{
extern char except_vec2_octeon;
set_handler(0x100, &except_vec2_octeon, 0x80);
}
/*
* Setup the Octeon cache flush routines
*
*/
void octeon_cache_init(void)
{
probe_octeon();
shm_align_mask = PAGE_SIZE - 1;
flush_cache_all = octeon_flush_icache_all;
__flush_cache_all = octeon_flush_icache_all;
flush_cache_mm = octeon_flush_cache_mm;
flush_cache_page = octeon_flush_cache_page;
flush_cache_range = octeon_flush_cache_range;
flush_icache_all = octeon_flush_icache_all;
flush_data_cache_page = octeon_flush_data_cache_page;
flush_icache_range = octeon_flush_icache_range;
local_flush_icache_range = local_octeon_flush_icache_range;
__flush_icache_user_range = octeon_flush_icache_range;
__local_flush_icache_user_range = local_octeon_flush_icache_range;
__flush_kernel_vmap_range = octeon_flush_kernel_vmap_range;
build_clear_page();
build_copy_page();
board_cache_error_setup = octeon_cache_error_setup;
}
/*
* Handle a cache error exception
*/
static RAW_NOTIFIER_HEAD(co_cache_error_chain);
int register_co_cache_error_notifier(struct notifier_block *nb)
{
return raw_notifier_chain_register(&co_cache_error_chain, nb);
}
EXPORT_SYMBOL_GPL(register_co_cache_error_notifier);
int unregister_co_cache_error_notifier(struct notifier_block *nb)
{
return raw_notifier_chain_unregister(&co_cache_error_chain, nb);
}
EXPORT_SYMBOL_GPL(unregister_co_cache_error_notifier);
static void co_cache_error_call_notifiers(unsigned long val)
{
int rv = raw_notifier_call_chain(&co_cache_error_chain, val, NULL);
if ((rv & ~NOTIFY_STOP_MASK) != NOTIFY_OK) {
u64 dcache_err;
unsigned long coreid = cvmx_get_core_num();
u64 icache_err = read_octeon_c0_icacheerr();
if (val) {
dcache_err = cache_err_dcache[coreid];
cache_err_dcache[coreid] = 0;
} else {
dcache_err = read_octeon_c0_dcacheerr();
}
pr_err("Core%lu: Cache error exception:\n", coreid);
pr_err("cp0_errorepc == %lx\n", read_c0_errorepc());
if (icache_err & 1) {
pr_err("CacheErr (Icache) == %llx\n",
(unsigned long long)icache_err);
write_octeon_c0_icacheerr(0);
}
if (dcache_err & 1) {
pr_err("CacheErr (Dcache) == %llx\n",
(unsigned long long)dcache_err);
}
}
}
/*
* Called when the exception is recoverable
*/
asmlinkage void cache_parity_error_octeon_recoverable(void)
{
co_cache_error_call_notifiers(0);
}
/*
* Called when the exception is not recoverable
*/
asmlinkage void cache_parity_error_octeon_non_recoverable(void)
{
co_cache_error_call_notifiers(1);
panic("Can't handle cache error: nested exception");
}
+314
View File
@@ -0,0 +1,314 @@
// 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) 2001, 2004, 2007 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/isadep.h>
#include <asm/io.h>
#include <asm/bootinfo.h>
#include <asm/cpu.h>
static unsigned long icache_size, dcache_size; /* Size in bytes */
static unsigned long icache_lsize, dcache_lsize; /* Size in bytes */
unsigned long r3k_cache_size(unsigned long ca_flags)
{
unsigned long flags, status, dummy, size;
volatile unsigned long *p;
p = (volatile unsigned long *) KSEG0;
flags = read_c0_status();
/* isolate cache space */
write_c0_status((ca_flags|flags)&~ST0_IEC);
*p = 0xa5a55a5a;
dummy = *p;
status = read_c0_status();
if (dummy != 0xa5a55a5a || (status & ST0_CM)) {
size = 0;
} else {
for (size = 128; size <= 0x40000; size <<= 1)
*(p + size) = 0;
*p = -1;
for (size = 128;
(size <= 0x40000) && (*(p + size) == 0);
size <<= 1)
;
if (size > 0x40000)
size = 0;
}
write_c0_status(flags);
return size * sizeof(*p);
}
unsigned long r3k_cache_lsize(unsigned long ca_flags)
{
unsigned long flags, status, lsize, i;
volatile unsigned long *p;
p = (volatile unsigned long *) KSEG0;
flags = read_c0_status();
/* isolate cache space */
write_c0_status((ca_flags|flags)&~ST0_IEC);
for (i = 0; i < 128; i++)
*(p + i) = 0;
*(volatile unsigned char *)p = 0;
for (lsize = 1; lsize < 128; lsize <<= 1) {
*(p + lsize);
status = read_c0_status();
if (!(status & ST0_CM))
break;
}
for (i = 0; i < 128; i += lsize)
*(volatile unsigned char *)(p + i) = 0;
write_c0_status(flags);
return lsize * sizeof(*p);
}
static void r3k_probe_cache(void)
{
dcache_size = r3k_cache_size(ST0_ISC);
if (dcache_size)
dcache_lsize = r3k_cache_lsize(ST0_ISC);
icache_size = r3k_cache_size(ST0_ISC|ST0_SWC);
if (icache_size)
icache_lsize = r3k_cache_lsize(ST0_ISC|ST0_SWC);
}
static void r3k_flush_icache_range(unsigned long start, unsigned long end)
{
unsigned long size, i, flags;
volatile unsigned char *p;
size = end - start;
if (size > icache_size || KSEGX(start) != KSEG0) {
start = KSEG0;
size = icache_size;
}
p = (char *)start;
flags = read_c0_status();
/* isolate cache space */
write_c0_status((ST0_ISC|ST0_SWC|flags)&~ST0_IEC);
for (i = 0; i < size; i += 0x080) {
asm( "sb\t$0, 0x000(%0)\n\t"
"sb\t$0, 0x004(%0)\n\t"
"sb\t$0, 0x008(%0)\n\t"
"sb\t$0, 0x00c(%0)\n\t"
"sb\t$0, 0x010(%0)\n\t"
"sb\t$0, 0x014(%0)\n\t"
"sb\t$0, 0x018(%0)\n\t"
"sb\t$0, 0x01c(%0)\n\t"
"sb\t$0, 0x020(%0)\n\t"
"sb\t$0, 0x024(%0)\n\t"
"sb\t$0, 0x028(%0)\n\t"
"sb\t$0, 0x02c(%0)\n\t"
"sb\t$0, 0x030(%0)\n\t"
"sb\t$0, 0x034(%0)\n\t"
"sb\t$0, 0x038(%0)\n\t"
"sb\t$0, 0x03c(%0)\n\t"
"sb\t$0, 0x040(%0)\n\t"
"sb\t$0, 0x044(%0)\n\t"
"sb\t$0, 0x048(%0)\n\t"
"sb\t$0, 0x04c(%0)\n\t"
"sb\t$0, 0x050(%0)\n\t"
"sb\t$0, 0x054(%0)\n\t"
"sb\t$0, 0x058(%0)\n\t"
"sb\t$0, 0x05c(%0)\n\t"
"sb\t$0, 0x060(%0)\n\t"
"sb\t$0, 0x064(%0)\n\t"
"sb\t$0, 0x068(%0)\n\t"
"sb\t$0, 0x06c(%0)\n\t"
"sb\t$0, 0x070(%0)\n\t"
"sb\t$0, 0x074(%0)\n\t"
"sb\t$0, 0x078(%0)\n\t"
"sb\t$0, 0x07c(%0)\n\t"
: : "r" (p) );
p += 0x080;
}
write_c0_status(flags);
}
static void r3k_flush_dcache_range(unsigned long start, unsigned long end)
{
unsigned long size, i, flags;
volatile unsigned char *p;
size = end - start;
if (size > dcache_size || KSEGX(start) != KSEG0) {
start = KSEG0;
size = dcache_size;
}
p = (char *)start;
flags = read_c0_status();
/* isolate cache space */
write_c0_status((ST0_ISC|flags)&~ST0_IEC);
for (i = 0; i < size; i += 0x080) {
asm( "sb\t$0, 0x000(%0)\n\t"
"sb\t$0, 0x004(%0)\n\t"
"sb\t$0, 0x008(%0)\n\t"
"sb\t$0, 0x00c(%0)\n\t"
"sb\t$0, 0x010(%0)\n\t"
"sb\t$0, 0x014(%0)\n\t"
"sb\t$0, 0x018(%0)\n\t"
"sb\t$0, 0x01c(%0)\n\t"
"sb\t$0, 0x020(%0)\n\t"
"sb\t$0, 0x024(%0)\n\t"
"sb\t$0, 0x028(%0)\n\t"
"sb\t$0, 0x02c(%0)\n\t"
"sb\t$0, 0x030(%0)\n\t"
"sb\t$0, 0x034(%0)\n\t"
"sb\t$0, 0x038(%0)\n\t"
"sb\t$0, 0x03c(%0)\n\t"
"sb\t$0, 0x040(%0)\n\t"
"sb\t$0, 0x044(%0)\n\t"
"sb\t$0, 0x048(%0)\n\t"
"sb\t$0, 0x04c(%0)\n\t"
"sb\t$0, 0x050(%0)\n\t"
"sb\t$0, 0x054(%0)\n\t"
"sb\t$0, 0x058(%0)\n\t"
"sb\t$0, 0x05c(%0)\n\t"
"sb\t$0, 0x060(%0)\n\t"
"sb\t$0, 0x064(%0)\n\t"
"sb\t$0, 0x068(%0)\n\t"
"sb\t$0, 0x06c(%0)\n\t"
"sb\t$0, 0x070(%0)\n\t"
"sb\t$0, 0x074(%0)\n\t"
"sb\t$0, 0x078(%0)\n\t"
"sb\t$0, 0x07c(%0)\n\t"
: : "r" (p) );
p += 0x080;
}
write_c0_status(flags);
}
static inline void r3k_flush_cache_all(void)
{
}
static inline void r3k___flush_cache_all(void)
{
r3k_flush_dcache_range(KSEG0, KSEG0 + dcache_size);
r3k_flush_icache_range(KSEG0, KSEG0 + icache_size);
}
static void r3k_flush_cache_mm(struct mm_struct *mm)
{
}
static void r3k_flush_cache_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end)
{
}
static void r3k_flush_cache_page(struct vm_area_struct *vma,
unsigned long addr, unsigned long pfn)
{
unsigned long kaddr = KSEG0ADDR(pfn << PAGE_SHIFT);
int exec = vma->vm_flags & VM_EXEC;
struct mm_struct *mm = vma->vm_mm;
pmd_t *pmdp;
pte_t *ptep;
pr_debug("cpage[%08llx,%08lx]\n",
cpu_context(smp_processor_id(), mm), addr);
/* No ASID => no such page in the cache. */
if (cpu_context(smp_processor_id(), mm) == 0)
return;
pmdp = pmd_off(mm, addr);
ptep = pte_offset_kernel(pmdp, addr);
/* Invalid => no such page in the cache. */
if (!(pte_val(*ptep) & _PAGE_PRESENT))
return;
r3k_flush_dcache_range(kaddr, kaddr + PAGE_SIZE);
if (exec)
r3k_flush_icache_range(kaddr, kaddr + PAGE_SIZE);
}
static void r3k_flush_data_cache_page(unsigned long addr)
{
}
static void r3k_flush_kernel_vmap_range(unsigned long vaddr, int size)
{
BUG();
}
static void r3k_dma_cache_wback_inv(unsigned long start, unsigned long size)
{
/* Catch bad driver code */
BUG_ON(size == 0);
iob();
r3k_flush_dcache_range(start, start + size);
}
void r3k_cache_init(void)
{
extern void build_clear_page(void);
extern void build_copy_page(void);
r3k_probe_cache();
flush_cache_all = r3k_flush_cache_all;
__flush_cache_all = r3k___flush_cache_all;
flush_cache_mm = r3k_flush_cache_mm;
flush_cache_range = r3k_flush_cache_range;
flush_cache_page = r3k_flush_cache_page;
flush_icache_range = r3k_flush_icache_range;
local_flush_icache_range = r3k_flush_icache_range;
__flush_icache_user_range = r3k_flush_icache_range;
__local_flush_icache_user_range = r3k_flush_icache_range;
__flush_kernel_vmap_range = r3k_flush_kernel_vmap_range;
flush_data_cache_page = r3k_flush_data_cache_page;
_dma_cache_wback_inv = r3k_dma_cache_wback_inv;
_dma_cache_wback = r3k_dma_cache_wback_inv;
_dma_cache_inv = r3k_dma_cache_wback_inv;
pr_info("Primary instruction cache %ldkB, linesize %ld bytes.\n",
icache_size >> 10, icache_lsize);
pr_info("Primary data cache %ldkB, linesize %ld bytes.\n",
dcache_size >> 10, dcache_lsize);
build_clear_page();
build_copy_page();
}
File diff suppressed because it is too large Load Diff
+218
View File
@@ -0,0 +1,218 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1994 - 2003, 06, 07 by Ralf Baechle (ralf@linux-mips.org)
* Copyright (C) 2007 MIPS Technologies, Inc.
*/
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/kernel.h>
#include <linux/linkage.h>
#include <linux/export.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/pagemap.h>
#include <asm/bcache.h>
#include <asm/cacheflush.h>
#include <asm/processor.h>
#include <asm/cpu.h>
#include <asm/cpu-features.h>
#include <asm/setup.h>
#include <asm/pgtable.h>
/* Cache operations. */
void (*flush_cache_all)(void);
void (*__flush_cache_all)(void);
EXPORT_SYMBOL_GPL(__flush_cache_all);
void (*flush_cache_mm)(struct mm_struct *mm);
void (*flush_cache_range)(struct vm_area_struct *vma, unsigned long start,
unsigned long end);
void (*flush_cache_page)(struct vm_area_struct *vma, unsigned long page,
unsigned long pfn);
void (*flush_icache_range)(unsigned long start, unsigned long end);
EXPORT_SYMBOL_GPL(flush_icache_range);
void (*local_flush_icache_range)(unsigned long start, unsigned long end);
EXPORT_SYMBOL_GPL(local_flush_icache_range);
void (*__flush_icache_user_range)(unsigned long start, unsigned long end);
void (*__local_flush_icache_user_range)(unsigned long start, unsigned long end);
EXPORT_SYMBOL_GPL(__local_flush_icache_user_range);
void (*__flush_cache_vmap)(void);
void (*__flush_cache_vunmap)(void);
void (*__flush_kernel_vmap_range)(unsigned long vaddr, int size);
EXPORT_SYMBOL_GPL(__flush_kernel_vmap_range);
/* MIPS specific cache operations */
void (*flush_data_cache_page)(unsigned long addr);
void (*flush_icache_all)(void);
EXPORT_SYMBOL(flush_data_cache_page);
EXPORT_SYMBOL(flush_icache_all);
/*
* Dummy cache handling routine
*/
void cache_noop(void) {}
#ifdef CONFIG_BOARD_SCACHE
static struct bcache_ops no_sc_ops = {
.bc_enable = (void *)cache_noop,
.bc_disable = (void *)cache_noop,
.bc_wback_inv = (void *)cache_noop,
.bc_inv = (void *)cache_noop
};
struct bcache_ops *bcops = &no_sc_ops;
#endif
#ifdef CONFIG_DMA_NONCOHERENT
/* DMA cache operations. */
void (*_dma_cache_wback_inv)(unsigned long start, unsigned long size);
void (*_dma_cache_wback)(unsigned long start, unsigned long size);
void (*_dma_cache_inv)(unsigned long start, unsigned long size);
#endif /* CONFIG_DMA_NONCOHERENT */
/*
* We could optimize the case where the cache argument is not BCACHE but
* that seems very atypical use ...
*/
SYSCALL_DEFINE3(cacheflush, unsigned long, addr, unsigned long, bytes,
unsigned int, cache)
{
if (bytes == 0)
return 0;
if (!access_ok((void __user *) addr, bytes))
return -EFAULT;
__flush_icache_user_range(addr, addr + bytes);
return 0;
}
void __flush_dcache_folio_pages(struct folio *folio, struct page *page,
unsigned int nr)
{
struct address_space *mapping = folio_flush_mapping(folio);
unsigned long addr;
unsigned int i;
if (mapping && !mapping_mapped(mapping)) {
folio_set_dcache_dirty(folio);
return;
}
/*
* We could delay the flush for the !folio_mapping case too. But that
* case is for exec env/arg pages and those are %99 certainly going to
* get faulted into the tlb (and thus flushed) anyways.
*/
for (i = 0; i < nr; i++) {
addr = (unsigned long)kmap_local_page(page + i);
flush_data_cache_page(addr);
kunmap_local((void *)addr);
}
}
EXPORT_SYMBOL(__flush_dcache_folio_pages);
void __flush_anon_page(struct page *page, unsigned long vmaddr)
{
unsigned long addr = (unsigned long) page_address(page);
struct folio *folio = page_folio(page);
if (pages_do_alias(addr, vmaddr)) {
if (folio_mapped(folio) && !folio_test_dcache_dirty(folio)) {
void *kaddr;
kaddr = kmap_coherent(page, vmaddr);
flush_data_cache_page((unsigned long)kaddr);
kunmap_coherent();
} else
flush_data_cache_page(addr);
}
}
EXPORT_SYMBOL(__flush_anon_page);
void __update_cache(unsigned long address, pte_t pte)
{
struct folio *folio;
unsigned long pfn, addr;
int exec = !pte_no_exec(pte) && !cpu_has_ic_fills_f_dc;
unsigned int i;
pfn = pte_pfn(pte);
if (unlikely(!pfn_valid(pfn)))
return;
folio = page_folio(pfn_to_page(pfn));
address &= PAGE_MASK;
address -= offset_in_folio(folio, pfn << PAGE_SHIFT);
if (folio_test_dcache_dirty(folio)) {
for (i = 0; i < folio_nr_pages(folio); i++) {
addr = (unsigned long)kmap_local_folio(folio, i);
if (exec || pages_do_alias(addr, address))
flush_data_cache_page(addr);
kunmap_local((void *)addr);
address += PAGE_SIZE;
}
folio_clear_dcache_dirty(folio);
}
}
unsigned long _page_cachable_default;
EXPORT_SYMBOL(_page_cachable_default);
#define PM(p) __pgprot(_page_cachable_default | (p))
static pgprot_t protection_map[16] __ro_after_init;
DECLARE_VM_GET_PAGE_PROT
static inline void setup_protection_map(void)
{
protection_map[0] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC | _PAGE_NO_READ);
protection_map[1] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC);
protection_map[2] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC | _PAGE_NO_READ);
protection_map[3] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC);
protection_map[4] = PM(_PAGE_PRESENT);
protection_map[5] = PM(_PAGE_PRESENT);
protection_map[6] = PM(_PAGE_PRESENT);
protection_map[7] = PM(_PAGE_PRESENT);
protection_map[8] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC | _PAGE_NO_READ);
protection_map[9] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC);
protection_map[10] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC | _PAGE_WRITE |
_PAGE_NO_READ);
protection_map[11] = PM(_PAGE_PRESENT | _PAGE_NO_EXEC | _PAGE_WRITE);
protection_map[12] = PM(_PAGE_PRESENT);
protection_map[13] = PM(_PAGE_PRESENT);
protection_map[14] = PM(_PAGE_PRESENT | _PAGE_WRITE);
protection_map[15] = PM(_PAGE_PRESENT | _PAGE_WRITE);
}
#undef PM
void cpu_cache_init(void)
{
if (IS_ENABLED(CONFIG_CPU_R3000) && cpu_has_3k_cache)
r3k_cache_init();
if ((IS_ENABLED(CONFIG_CPU_R4K_CACHE_TLB) ||
IS_ENABLED(CONFIG_CPU_SB1)) && cpu_has_4k_cache)
r4k_cache_init();
if (IS_ENABLED(CONFIG_CPU_CAVIUM_OCTEON) && cpu_has_octeon_cache)
octeon_cache_init();
setup_protection_map();
}
+569
View File
@@ -0,0 +1,569 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2001,2002,2003 Broadcom Corporation
*/
#include <linux/sched.h>
#include <asm/mipsregs.h>
#include <asm/sibyte/sb1250.h>
#include <asm/sibyte/sb1250_regs.h>
#if !defined(CONFIG_SIBYTE_BUS_WATCHER) || defined(CONFIG_SIBYTE_BW_TRACE)
#include <asm/io.h>
#include <asm/sibyte/sb1250_scd.h>
#endif
/*
* We'd like to dump the L2_ECC_TAG register on errors, but errata make
* that unsafe... So for now we don't. (BCM1250/BCM112x erratum SOC-48.)
*/
#undef DUMP_L2_ECC_TAG_ON_ERROR
/* SB1 definitions */
/* XXX should come from config1 XXX */
#define SB1_CACHE_INDEX_MASK 0x1fe0
#define CP0_ERRCTL_RECOVERABLE (1 << 31)
#define CP0_ERRCTL_DCACHE (1 << 30)
#define CP0_ERRCTL_ICACHE (1 << 29)
#define CP0_ERRCTL_MULTIBUS (1 << 23)
#define CP0_ERRCTL_MC_TLB (1 << 15)
#define CP0_ERRCTL_MC_TIMEOUT (1 << 14)
#define CP0_CERRI_TAG_PARITY (1 << 29)
#define CP0_CERRI_DATA_PARITY (1 << 28)
#define CP0_CERRI_EXTERNAL (1 << 26)
#define CP0_CERRI_IDX_VALID(c) (!((c) & CP0_CERRI_EXTERNAL))
#define CP0_CERRI_DATA (CP0_CERRI_DATA_PARITY)
#define CP0_CERRD_MULTIPLE (1 << 31)
#define CP0_CERRD_TAG_STATE (1 << 30)
#define CP0_CERRD_TAG_ADDRESS (1 << 29)
#define CP0_CERRD_DATA_SBE (1 << 28)
#define CP0_CERRD_DATA_DBE (1 << 27)
#define CP0_CERRD_EXTERNAL (1 << 26)
#define CP0_CERRD_LOAD (1 << 25)
#define CP0_CERRD_STORE (1 << 24)
#define CP0_CERRD_FILLWB (1 << 23)
#define CP0_CERRD_COHERENCY (1 << 22)
#define CP0_CERRD_DUPTAG (1 << 21)
#define CP0_CERRD_DPA_VALID(c) (!((c) & CP0_CERRD_EXTERNAL))
#define CP0_CERRD_IDX_VALID(c) \
(((c) & (CP0_CERRD_LOAD | CP0_CERRD_STORE)) ? (!((c) & CP0_CERRD_EXTERNAL)) : 0)
#define CP0_CERRD_CAUSES \
(CP0_CERRD_LOAD | CP0_CERRD_STORE | CP0_CERRD_FILLWB | CP0_CERRD_COHERENCY | CP0_CERRD_DUPTAG)
#define CP0_CERRD_TYPES \
(CP0_CERRD_TAG_STATE | CP0_CERRD_TAG_ADDRESS | CP0_CERRD_DATA_SBE | CP0_CERRD_DATA_DBE | CP0_CERRD_EXTERNAL)
#define CP0_CERRD_DATA (CP0_CERRD_DATA_SBE | CP0_CERRD_DATA_DBE)
static uint32_t extract_ic(unsigned short addr, int data);
static uint32_t extract_dc(unsigned short addr, int data);
static inline void breakout_errctl(unsigned int val)
{
if (val & CP0_ERRCTL_RECOVERABLE)
printk(" recoverable");
if (val & CP0_ERRCTL_DCACHE)
printk(" dcache");
if (val & CP0_ERRCTL_ICACHE)
printk(" icache");
if (val & CP0_ERRCTL_MULTIBUS)
printk(" multiple-buserr");
printk("\n");
}
static inline void breakout_cerri(unsigned int val)
{
if (val & CP0_CERRI_TAG_PARITY)
printk(" tag-parity");
if (val & CP0_CERRI_DATA_PARITY)
printk(" data-parity");
if (val & CP0_CERRI_EXTERNAL)
printk(" external");
printk("\n");
}
static inline void breakout_cerrd(unsigned int val)
{
switch (val & CP0_CERRD_CAUSES) {
case CP0_CERRD_LOAD:
printk(" load,");
break;
case CP0_CERRD_STORE:
printk(" store,");
break;
case CP0_CERRD_FILLWB:
printk(" fill/wb,");
break;
case CP0_CERRD_COHERENCY:
printk(" coherency,");
break;
case CP0_CERRD_DUPTAG:
printk(" duptags,");
break;
default:
printk(" NO CAUSE,");
break;
}
if (!(val & CP0_CERRD_TYPES))
printk(" NO TYPE");
else {
if (val & CP0_CERRD_MULTIPLE)
printk(" multi-err");
if (val & CP0_CERRD_TAG_STATE)
printk(" tag-state");
if (val & CP0_CERRD_TAG_ADDRESS)
printk(" tag-address");
if (val & CP0_CERRD_DATA_SBE)
printk(" data-SBE");
if (val & CP0_CERRD_DATA_DBE)
printk(" data-DBE");
if (val & CP0_CERRD_EXTERNAL)
printk(" external");
}
printk("\n");
}
#ifndef CONFIG_SIBYTE_BUS_WATCHER
static void check_bus_watcher(void)
{
uint32_t status, l2_err, memio_err;
#ifdef DUMP_L2_ECC_TAG_ON_ERROR
uint64_t l2_tag;
#endif
/* Destructive read, clears register and interrupt */
status = csr_in32(IOADDR(A_SCD_BUS_ERR_STATUS));
/* Bit 31 is always on, but there's no #define for that */
if (status & ~(1UL << 31)) {
l2_err = csr_in32(IOADDR(A_BUS_L2_ERRORS));
#ifdef DUMP_L2_ECC_TAG_ON_ERROR
l2_tag = in64(IOADDR(A_L2_ECC_TAG));
#endif
memio_err = csr_in32(IOADDR(A_BUS_MEM_IO_ERRORS));
printk("Bus watcher error counters: %08x %08x\n", l2_err, memio_err);
printk("\nLast recorded signature:\n");
printk("Request %02x from %d, answered by %d with Dcode %d\n",
(unsigned int)(G_SCD_BERR_TID(status) & 0x3f),
(int)(G_SCD_BERR_TID(status) >> 6),
(int)G_SCD_BERR_RID(status),
(int)G_SCD_BERR_DCODE(status));
#ifdef DUMP_L2_ECC_TAG_ON_ERROR
printk("Last L2 tag w/ bad ECC: %016llx\n", l2_tag);
#endif
} else {
printk("Bus watcher indicates no error\n");
}
}
#else
extern void check_bus_watcher(void);
#endif
asmlinkage void sb1_cache_error(void)
{
uint32_t errctl, cerr_i, cerr_d, dpalo, dpahi, eepc, res;
unsigned long long cerr_dpa;
#ifdef CONFIG_SIBYTE_BW_TRACE
/* Freeze the trace buffer now */
csr_out32(M_SCD_TRACE_CFG_FREEZE, IOADDR(A_SCD_TRACE_CFG));
printk("Trace buffer frozen\n");
#endif
printk("Cache error exception on CPU %x:\n",
(read_c0_prid() >> 25) & 0x7);
__asm__ __volatile__ (
" .set push\n\t"
" .set mips64\n\t"
" .set noat\n\t"
" mfc0 %0, $26\n\t"
" mfc0 %1, $27\n\t"
" mfc0 %2, $27, 1\n\t"
" dmfc0 $1, $27, 3\n\t"
" dsrl32 %3, $1, 0 \n\t"
" sll %4, $1, 0 \n\t"
" mfc0 %5, $30\n\t"
" .set pop"
: "=r" (errctl), "=r" (cerr_i), "=r" (cerr_d),
"=r" (dpahi), "=r" (dpalo), "=r" (eepc));
cerr_dpa = (((uint64_t)dpahi) << 32) | dpalo;
printk(" c0_errorepc == %08x\n", eepc);
printk(" c0_errctl == %08x", errctl);
breakout_errctl(errctl);
if (errctl & CP0_ERRCTL_ICACHE) {
printk(" c0_cerr_i == %08x", cerr_i);
breakout_cerri(cerr_i);
if (CP0_CERRI_IDX_VALID(cerr_i)) {
/* Check index of EPC, allowing for delay slot */
if (((eepc & SB1_CACHE_INDEX_MASK) != (cerr_i & SB1_CACHE_INDEX_MASK)) &&
((eepc & SB1_CACHE_INDEX_MASK) != ((cerr_i & SB1_CACHE_INDEX_MASK) - 4)))
printk(" cerr_i idx doesn't match eepc\n");
else {
res = extract_ic(cerr_i & SB1_CACHE_INDEX_MASK,
(cerr_i & CP0_CERRI_DATA) != 0);
if (!(res & cerr_i))
printk("...didn't see indicated icache problem\n");
}
}
}
if (errctl & CP0_ERRCTL_DCACHE) {
printk(" c0_cerr_d == %08x", cerr_d);
breakout_cerrd(cerr_d);
if (CP0_CERRD_DPA_VALID(cerr_d)) {
printk(" c0_cerr_dpa == %010llx\n", cerr_dpa);
if (!CP0_CERRD_IDX_VALID(cerr_d)) {
res = extract_dc(cerr_dpa & SB1_CACHE_INDEX_MASK,
(cerr_d & CP0_CERRD_DATA) != 0);
if (!(res & cerr_d))
printk("...didn't see indicated dcache problem\n");
} else {
if ((cerr_dpa & SB1_CACHE_INDEX_MASK) != (cerr_d & SB1_CACHE_INDEX_MASK))
printk(" cerr_d idx doesn't match cerr_dpa\n");
else {
res = extract_dc(cerr_d & SB1_CACHE_INDEX_MASK,
(cerr_d & CP0_CERRD_DATA) != 0);
if (!(res & cerr_d))
printk("...didn't see indicated problem\n");
}
}
}
}
check_bus_watcher();
/*
* Calling panic() when a fatal cache error occurs scrambles the
* state of the system (and the cache), making it difficult to
* investigate after the fact. However, if you just stall the CPU,
* the other CPU may keep on running, which is typically very
* undesirable.
*/
#ifdef CONFIG_SB1_CERR_STALL
while (1)
;
#else
panic("unhandled cache error");
#endif
}
/* Parity lookup table. */
static const uint8_t parity[256] = {
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0
};
/* Masks to select bits for Hamming parity, mask_72_64[i] for bit[i] */
static const uint64_t mask_72_64[8] = {
0x0738C808099264FFULL,
0x38C808099264FF07ULL,
0xC808099264FF0738ULL,
0x08099264FF0738C8ULL,
0x099264FF0738C808ULL,
0x9264FF0738C80809ULL,
0x64FF0738C8080992ULL,
0xFF0738C808099264ULL
};
/* Calculate the parity on a range of bits */
static char range_parity(uint64_t dword, int max, int min)
{
char parity = 0;
int i;
dword >>= min;
for (i=max-min; i>=0; i--) {
if (dword & 0x1)
parity = !parity;
dword >>= 1;
}
return parity;
}
/* Calculate the 4-bit even byte-parity for an instruction */
static unsigned char inst_parity(uint32_t word)
{
int i, j;
char parity = 0;
for (j=0; j<4; j++) {
char byte_parity = 0;
for (i=0; i<8; i++) {
if (word & 0x80000000)
byte_parity = !byte_parity;
word <<= 1;
}
parity <<= 1;
parity |= byte_parity;
}
return parity;
}
static uint32_t extract_ic(unsigned short addr, int data)
{
unsigned short way;
int valid;
uint32_t taghi, taglolo, taglohi;
unsigned long long taglo, va;
uint64_t tlo_tmp;
uint8_t lru;
int res = 0;
printk("Icache index 0x%04x ", addr);
for (way = 0; way < 4; way++) {
/* Index-load-tag-I */
__asm__ __volatile__ (
" .set push \n\t"
" .set noreorder \n\t"
" .set mips64 \n\t"
" .set noat \n\t"
" cache 4, 0(%3) \n\t"
" mfc0 %0, $29 \n\t"
" dmfc0 $1, $28 \n\t"
" dsrl32 %1, $1, 0 \n\t"
" sll %2, $1, 0 \n\t"
" .set pop"
: "=r" (taghi), "=r" (taglohi), "=r" (taglolo)
: "r" ((way << 13) | addr));
taglo = ((unsigned long long)taglohi << 32) | taglolo;
if (way == 0) {
lru = (taghi >> 14) & 0xff;
printk("[Bank %d Set 0x%02x] LRU > %d %d %d %d > MRU\n",
((addr >> 5) & 0x3), /* bank */
((addr >> 7) & 0x3f), /* index */
(lru & 0x3),
((lru >> 2) & 0x3),
((lru >> 4) & 0x3),
((lru >> 6) & 0x3));
}
va = (taglo & 0xC0000FFFFFFFE000ULL) | addr;
if ((taglo & (1 << 31)) && (((taglo >> 62) & 0x3) == 3))
va |= 0x3FFFF00000000000ULL;
valid = ((taghi >> 29) & 1);
if (valid) {
tlo_tmp = taglo & 0xfff3ff;
if (((taglo >> 10) & 1) ^ range_parity(tlo_tmp, 23, 0)) {
printk(" ** bad parity in VTag0/G/ASID\n");
res |= CP0_CERRI_TAG_PARITY;
}
if (((taglo >> 11) & 1) ^ range_parity(taglo, 63, 24)) {
printk(" ** bad parity in R/VTag1\n");
res |= CP0_CERRI_TAG_PARITY;
}
}
if (valid ^ ((taghi >> 27) & 1)) {
printk(" ** bad parity for valid bit\n");
res |= CP0_CERRI_TAG_PARITY;
}
printk(" %d [VA %016llx] [Vld? %d] raw tags: %08X-%016llX\n",
way, va, valid, taghi, taglo);
if (data) {
uint32_t datahi, insta, instb;
uint8_t predecode;
int offset;
/* (hit all banks and ways) */
for (offset = 0; offset < 4; offset++) {
/* Index-load-data-I */
__asm__ __volatile__ (
" .set push\n\t"
" .set noreorder\n\t"
" .set mips64\n\t"
" .set noat\n\t"
" cache 6, 0(%3) \n\t"
" mfc0 %0, $29, 1\n\t"
" dmfc0 $1, $28, 1\n\t"
" dsrl32 %1, $1, 0 \n\t"
" sll %2, $1, 0 \n\t"
" .set pop \n"
: "=r" (datahi), "=r" (insta), "=r" (instb)
: "r" ((way << 13) | addr | (offset << 3)));
predecode = (datahi >> 8) & 0xff;
if (((datahi >> 16) & 1) != (uint32_t)range_parity(predecode, 7, 0)) {
printk(" ** bad parity in predecode\n");
res |= CP0_CERRI_DATA_PARITY;
}
/* XXXKW should/could check predecode bits themselves */
if (((datahi >> 4) & 0xf) ^ inst_parity(insta)) {
printk(" ** bad parity in instruction a\n");
res |= CP0_CERRI_DATA_PARITY;
}
if ((datahi & 0xf) ^ inst_parity(instb)) {
printk(" ** bad parity in instruction b\n");
res |= CP0_CERRI_DATA_PARITY;
}
printk(" %05X-%08X%08X", datahi, insta, instb);
}
printk("\n");
}
}
return res;
}
/* Compute the ECC for a data doubleword */
static uint8_t dc_ecc(uint64_t dword)
{
uint64_t t;
uint32_t w;
uint8_t p;
int i;
p = 0;
for (i = 7; i >= 0; i--)
{
p <<= 1;
t = dword & mask_72_64[i];
w = (uint32_t)(t >> 32);
p ^= (parity[w>>24] ^ parity[(w>>16) & 0xFF]
^ parity[(w>>8) & 0xFF] ^ parity[w & 0xFF]);
w = (uint32_t)(t & 0xFFFFFFFF);
p ^= (parity[w>>24] ^ parity[(w>>16) & 0xFF]
^ parity[(w>>8) & 0xFF] ^ parity[w & 0xFF]);
}
return p;
}
struct dc_state {
unsigned char val;
char *name;
};
static struct dc_state dc_states[] = {
{ 0x00, "INVALID" },
{ 0x0f, "COH-SHD" },
{ 0x13, "NCO-E-C" },
{ 0x19, "NCO-E-D" },
{ 0x16, "COH-E-C" },
{ 0x1c, "COH-E-D" },
{ 0xff, "*ERROR*" }
};
#define DC_TAG_VALID(state) \
(((state) == 0x0) || ((state) == 0xf) || ((state) == 0x13) || \
((state) == 0x19) || ((state) == 0x16) || ((state) == 0x1c))
static char *dc_state_str(unsigned char state)
{
struct dc_state *dsc = dc_states;
while (dsc->val != 0xff) {
if (dsc->val == state)
break;
dsc++;
}
return dsc->name;
}
static uint32_t extract_dc(unsigned short addr, int data)
{
int valid, way;
unsigned char state;
uint32_t taghi, taglolo, taglohi;
unsigned long long taglo, pa;
uint8_t ecc, lru;
int res = 0;
printk("Dcache index 0x%04x ", addr);
for (way = 0; way < 4; way++) {
__asm__ __volatile__ (
" .set push\n\t"
" .set noreorder\n\t"
" .set mips64\n\t"
" .set noat\n\t"
" cache 5, 0(%3)\n\t" /* Index-load-tag-D */
" mfc0 %0, $29, 2\n\t"
" dmfc0 $1, $28, 2\n\t"
" dsrl32 %1, $1, 0\n\t"
" sll %2, $1, 0\n\t"
" .set pop"
: "=r" (taghi), "=r" (taglohi), "=r" (taglolo)
: "r" ((way << 13) | addr));
taglo = ((unsigned long long)taglohi << 32) | taglolo;
pa = (taglo & 0xFFFFFFE000ULL) | addr;
if (way == 0) {
lru = (taghi >> 14) & 0xff;
printk("[Bank %d Set 0x%02x] LRU > %d %d %d %d > MRU\n",
((addr >> 11) & 0x2) | ((addr >> 5) & 1), /* bank */
((addr >> 6) & 0x3f), /* index */
(lru & 0x3),
((lru >> 2) & 0x3),
((lru >> 4) & 0x3),
((lru >> 6) & 0x3));
}
state = (taghi >> 25) & 0x1f;
valid = DC_TAG_VALID(state);
printk(" %d [PA %010llx] [state %s (%02x)] raw tags: %08X-%016llX\n",
way, pa, dc_state_str(state), state, taghi, taglo);
if (valid) {
if (((taglo >> 11) & 1) ^ range_parity(taglo, 39, 26)) {
printk(" ** bad parity in PTag1\n");
res |= CP0_CERRD_TAG_ADDRESS;
}
if (((taglo >> 10) & 1) ^ range_parity(taglo, 25, 13)) {
printk(" ** bad parity in PTag0\n");
res |= CP0_CERRD_TAG_ADDRESS;
}
} else {
res |= CP0_CERRD_TAG_STATE;
}
if (data) {
uint32_t datalohi, datalolo, datahi;
unsigned long long datalo;
int offset;
char bad_ecc = 0;
for (offset = 0; offset < 4; offset++) {
/* Index-load-data-D */
__asm__ __volatile__ (
" .set push\n\t"
" .set noreorder\n\t"
" .set mips64\n\t"
" .set noat\n\t"
" cache 7, 0(%3)\n\t" /* Index-load-data-D */
" mfc0 %0, $29, 3\n\t"
" dmfc0 $1, $28, 3\n\t"
" dsrl32 %1, $1, 0 \n\t"
" sll %2, $1, 0 \n\t"
" .set pop"
: "=r" (datahi), "=r" (datalohi), "=r" (datalolo)
: "r" ((way << 13) | addr | (offset << 3)));
datalo = ((unsigned long long)datalohi << 32) | datalolo;
ecc = dc_ecc(datalo);
if (ecc != datahi) {
int bits;
bad_ecc |= 1 << (3-offset);
ecc ^= datahi;
bits = hweight8(ecc);
res |= (bits == 1) ? CP0_CERRD_DATA_SBE : CP0_CERRD_DATA_DBE;
}
printk(" %02X-%016llX", datahi, datalo);
}
printk("\n");
if (bad_ecc)
printk(" dwords w/ bad ECC: %d %d %d %d\n",
!!(bad_ecc & 8), !!(bad_ecc & 4),
!!(bad_ecc & 2), !!(bad_ecc & 1));
}
}
return res;
}
+42
View File
@@ -0,0 +1,42 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1995 - 1999 Ralf Baechle
* Copyright (C) 1999 Silicon Graphics, Inc.
*
* Cache error handler
*/
#include <asm/asm.h>
#include <asm/regdef.h>
#include <asm/mipsregs.h>
#include <asm/stackframe.h>
/*
* Game over. Go to the button. Press gently. Swear where allowed by
* legislation.
*/
LEAF(except_vec2_generic)
.set noreorder
.set noat
.set mips0
/*
* This is a very bad place to be. Our cache error
* detection has triggered. If we have write-back data
* in the cache, we may not be able to recover. As a
* first-order desperate measure, turn off KSEG0 caching.
*/
mfc0 k0,CP0_CONFIG
li k1,~CONF_CM_CMASK
and k0,k0,k1
ori k0,k0,CONF_CM_UNCACHED
mtc0 k0,CP0_CONFIG
/* Give it a few cycles to sink in... */
nop
nop
nop
j cache_parity_error
nop
END(except_vec2_generic)
+70
View File
@@ -0,0 +1,70 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2006 Cavium Networks
* Cache error handler
*/
#include <asm/asm.h>
#include <asm/regdef.h>
#include <asm/mipsregs.h>
#include <asm/stackframe.h>
/*
* Handle cache error. Indicate to the second level handler whether
* the exception is recoverable.
*/
LEAF(except_vec2_octeon)
.set push
.set mips64r2
.set noreorder
.set noat
/* due to an errata we need to read the COP0 CacheErr (Dcache)
* before any cache/DRAM access */
rdhwr k0, $0 /* get core_id */
PTR_LA k1, cache_err_dcache
sll k0, k0, 3
PTR_ADDU k1, k0, k1 /* k1 = &cache_err_dcache[core_id] */
dmfc0 k0, CP0_CACHEERR, 1
sd k0, (k1)
dmtc0 $0, CP0_CACHEERR, 1
/* check whether this is a nested exception */
mfc0 k1, CP0_STATUS
andi k1, k1, ST0_EXL
beqz k1, 1f
nop
j cache_parity_error_octeon_non_recoverable
nop
/* exception is recoverable */
1: j handle_cache_err
nop
.set pop
END(except_vec2_octeon)
/* We need to jump to handle_cache_err so that the previous handler
* can fit within 0x80 bytes. We also move from 0xFFFFFFFFAXXXXXXX
* space (uncached) to the 0xFFFFFFFF8XXXXXXX space (cached). */
LEAF(handle_cache_err)
.set push
.set noreorder
.set noat
SAVE_ALL
KMODE
jal cache_parity_error_octeon_recoverable
nop
j ret_from_exception
nop
.set pop
END(handle_cache_err)
+157
View File
@@ -0,0 +1,157 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2001,2002,2003 Broadcom Corporation
*/
#include <asm/asm.h>
#include <asm/regdef.h>
#include <asm/mipsregs.h>
#include <asm/stackframe.h>
#include <asm/cacheops.h>
#include <asm/sibyte/board.h>
#define C0_ERRCTL $26 /* CP0: Error info */
#define C0_CERR_I $27 /* CP0: Icache error */
#define C0_CERR_D $27,1 /* CP0: Dcache error */
/*
* Based on SiByte sample software cache-err/cerr.S
* CVS revision 1.8. Only the 'unrecoverable' case
* is changed.
*/
.set mips64
.set noreorder
.set noat
/*
* sb1_cerr_vec: code to be copied to the Cache Error
* Exception vector. The code must be pushed out to memory
* (either by copying to Kseg0 and Kseg1 both, or by flushing
* the L1 and L2) since it is fetched as 0xa0000100.
*
* NOTE: Be sure this handler is at most 28 instructions long
* since the final 16 bytes of the exception vector memory
* (0x170-0x17f) are used to preserve k0, k1, and ra.
*/
LEAF(except_vec2_sb1)
/*
* If this error is recoverable, we need to exit the handler
* without having dirtied any registers. To do this,
* save/restore k0 and k1 from low memory (Useg is direct
* mapped while ERL=1). Note that we can't save to a
* CPU-specific location without ruining a register in the
* process. This means we are vulnerable to data corruption
* whenever the handler is reentered by a second CPU.
*/
sd k0,0x170($0)
sd k1,0x178($0)
#ifdef CONFIG_SB1_CEX_ALWAYS_FATAL
j handle_vec2_sb1
nop
#else
/*
* M_ERRCTL_RECOVERABLE is bit 31, which makes it easy to tell
* if we can fast-path out of here for a h/w-recovered error.
*/
mfc0 k1,C0_ERRCTL
bgtz k1,attempt_recovery
sll k0,k1,1
recovered_dcache:
/*
* Unlock CacheErr-D (which in turn unlocks CacheErr-DPA).
* Ought to log the occurrence of this recovered dcache error.
*/
b recovered
mtc0 $0,C0_CERR_D
attempt_recovery:
/*
* k0 has C0_ERRCTL << 1, which puts 'DC' at bit 31. Any
* Dcache errors we can recover from will take more extensive
* processing. For now, they are considered "unrecoverable".
* Note that 'DC' becoming set (outside of ERL mode) will
* cause 'IC' to clear; so if there's an Icache error, we'll
* only find out about it if we recover from this error and
* continue executing.
*/
bltz k0,unrecoverable
sll k0,1
/*
* k0 has C0_ERRCTL << 2, which puts 'IC' at bit 31. If an
* Icache error isn't indicated, I'm not sure why we got here.
* Consider that case "unrecoverable" for now.
*/
bgez k0,unrecoverable
attempt_icache_recovery:
/*
* External icache errors are due to uncorrectable ECC errors
* in the L2 cache or Memory Controller and cannot be
* recovered here.
*/
mfc0 k0,C0_CERR_I /* delay slot */
li k1,1 << 26 /* ICACHE_EXTERNAL */
and k1,k0
bnez k1,unrecoverable
andi k0,0x1fe0
/*
* Since the error is internal, the 'IDX' field from
* CacheErr-I is valid and we can just invalidate all blocks
* in that set.
*/
cache Index_Invalidate_I,(0<<13)(k0)
cache Index_Invalidate_I,(1<<13)(k0)
cache Index_Invalidate_I,(2<<13)(k0)
cache Index_Invalidate_I,(3<<13)(k0)
/* Ought to log this recovered icache error */
recovered:
/* Restore the saved registers */
ld k0,0x170($0)
ld k1,0x178($0)
eret
unrecoverable:
/* Unrecoverable Icache or Dcache error; log it and/or fail */
j handle_vec2_sb1
nop
#endif
END(except_vec2_sb1)
LEAF(handle_vec2_sb1)
mfc0 k0,CP0_CONFIG
li k1,~CONF_CM_CMASK
and k0,k0,k1
ori k0,k0,CONF_CM_UNCACHED
mtc0 k0,CP0_CONFIG
SSNOP
SSNOP
SSNOP
SSNOP
bnezl $0, 1f
1:
mfc0 k0, CP0_STATUS
sll k0, k0, 3 # check CU0 (kernel?)
bltz k0, 2f
nop
/* Get a valid Kseg0 stack pointer. Any task's stack pointer
* will do, although if we ever want to resume execution we
* better not have corrupted any state. */
get_saved_sp
move sp, k1
2:
j sb1_cache_error
nop
END(handle_vec2_sb1)
+290
View File
@@ -0,0 +1,290 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/atomic.h>
#include <linux/mmu_context.h>
#include <linux/percpu.h>
#include <linux/spinlock.h>
static DEFINE_RAW_SPINLOCK(cpu_mmid_lock);
static atomic64_t mmid_version;
static unsigned int num_mmids;
static unsigned long *mmid_map;
static DEFINE_PER_CPU(u64, reserved_mmids);
static cpumask_t tlb_flush_pending;
static bool asid_versions_eq(int cpu, u64 a, u64 b)
{
return ((a ^ b) & asid_version_mask(cpu)) == 0;
}
void get_new_mmu_context(struct mm_struct *mm)
{
unsigned int cpu;
u64 asid;
/*
* This function is specific to ASIDs, and should not be called when
* MMIDs are in use.
*/
if (WARN_ON(IS_ENABLED(CONFIG_DEBUG_VM) && cpu_has_mmid))
return;
cpu = smp_processor_id();
asid = asid_cache(cpu);
if (!((asid += cpu_asid_inc()) & cpu_asid_mask(&cpu_data[cpu]))) {
if (cpu_has_vtag_icache)
flush_icache_all();
local_flush_tlb_all(); /* start new asid cycle */
}
set_cpu_context(cpu, mm, asid);
asid_cache(cpu) = asid;
}
EXPORT_SYMBOL_GPL(get_new_mmu_context);
void check_mmu_context(struct mm_struct *mm)
{
unsigned int cpu = smp_processor_id();
/*
* This function is specific to ASIDs, and should not be called when
* MMIDs are in use.
*/
if (WARN_ON(IS_ENABLED(CONFIG_DEBUG_VM) && cpu_has_mmid))
return;
/* Check if our ASID is of an older version and thus invalid */
if (!asid_versions_eq(cpu, cpu_context(cpu, mm), asid_cache(cpu)))
get_new_mmu_context(mm);
}
EXPORT_SYMBOL_GPL(check_mmu_context);
static void flush_context(void)
{
u64 mmid;
int cpu;
/* Update the list of reserved MMIDs and the MMID bitmap */
bitmap_zero(mmid_map, num_mmids);
/* Reserve an MMID for kmap/wired entries */
__set_bit(MMID_KERNEL_WIRED, mmid_map);
for_each_possible_cpu(cpu) {
mmid = xchg_relaxed(&cpu_data[cpu].asid_cache, 0);
/*
* If this CPU has already been through a
* rollover, but hasn't run another task in
* the meantime, we must preserve its reserved
* MMID, as this is the only trace we have of
* the process it is still running.
*/
if (mmid == 0)
mmid = per_cpu(reserved_mmids, cpu);
__set_bit(mmid & cpu_asid_mask(&cpu_data[cpu]), mmid_map);
per_cpu(reserved_mmids, cpu) = mmid;
}
/*
* Queue a TLB invalidation for each CPU to perform on next
* context-switch
*/
cpumask_setall(&tlb_flush_pending);
}
static bool check_update_reserved_mmid(u64 mmid, u64 newmmid)
{
bool hit;
int cpu;
/*
* Iterate over the set of reserved MMIDs looking for a match.
* If we find one, then we can update our mm to use newmmid
* (i.e. the same MMID in the current generation) but we can't
* exit the loop early, since we need to ensure that all copies
* of the old MMID are updated to reflect the mm. Failure to do
* so could result in us missing the reserved MMID in a future
* generation.
*/
hit = false;
for_each_possible_cpu(cpu) {
if (per_cpu(reserved_mmids, cpu) == mmid) {
hit = true;
per_cpu(reserved_mmids, cpu) = newmmid;
}
}
return hit;
}
static u64 get_new_mmid(struct mm_struct *mm)
{
static u32 cur_idx = MMID_KERNEL_WIRED + 1;
u64 mmid, version, mmid_mask;
mmid = cpu_context(0, mm);
version = atomic64_read(&mmid_version);
mmid_mask = cpu_asid_mask(&boot_cpu_data);
if (!asid_versions_eq(0, mmid, 0)) {
u64 newmmid = version | (mmid & mmid_mask);
/*
* If our current MMID was active during a rollover, we
* can continue to use it and this was just a false alarm.
*/
if (check_update_reserved_mmid(mmid, newmmid)) {
mmid = newmmid;
goto set_context;
}
/*
* We had a valid MMID in a previous life, so try to re-use
* it if possible.
*/
if (!__test_and_set_bit(mmid & mmid_mask, mmid_map)) {
mmid = newmmid;
goto set_context;
}
}
/* Allocate a free MMID */
mmid = find_next_zero_bit(mmid_map, num_mmids, cur_idx);
if (mmid != num_mmids)
goto reserve_mmid;
/* We're out of MMIDs, so increment the global version */
version = atomic64_add_return_relaxed(asid_first_version(0),
&mmid_version);
/* Note currently active MMIDs & mark TLBs as requiring flushes */
flush_context();
/* We have more MMIDs than CPUs, so this will always succeed */
mmid = find_first_zero_bit(mmid_map, num_mmids);
reserve_mmid:
__set_bit(mmid, mmid_map);
cur_idx = mmid;
mmid |= version;
set_context:
set_cpu_context(0, mm, mmid);
return mmid;
}
void check_switch_mmu_context(struct mm_struct *mm)
{
unsigned int cpu = smp_processor_id();
u64 ctx, old_active_mmid;
unsigned long flags;
if (!cpu_has_mmid) {
check_mmu_context(mm);
write_c0_entryhi(cpu_asid(cpu, mm));
goto setup_pgd;
}
/*
* MMID switch fast-path, to avoid acquiring cpu_mmid_lock when it's
* unnecessary.
*
* The memory ordering here is subtle. If our active_mmids is non-zero
* and the MMID matches the current version, then we update the CPU's
* asid_cache with a relaxed cmpxchg. Racing with a concurrent rollover
* means that either:
*
* - We get a zero back from the cmpxchg and end up waiting on
* cpu_mmid_lock in check_mmu_context(). Taking the lock synchronises
* with the rollover and so we are forced to see the updated
* generation.
*
* - We get a valid MMID back from the cmpxchg, which means the
* relaxed xchg in flush_context will treat us as reserved
* because atomic RmWs are totally ordered for a given location.
*/
ctx = cpu_context(cpu, mm);
old_active_mmid = READ_ONCE(cpu_data[cpu].asid_cache);
if (!old_active_mmid ||
!asid_versions_eq(cpu, ctx, atomic64_read(&mmid_version)) ||
!cmpxchg_relaxed(&cpu_data[cpu].asid_cache, old_active_mmid, ctx)) {
raw_spin_lock_irqsave(&cpu_mmid_lock, flags);
ctx = cpu_context(cpu, mm);
if (!asid_versions_eq(cpu, ctx, atomic64_read(&mmid_version)))
ctx = get_new_mmid(mm);
WRITE_ONCE(cpu_data[cpu].asid_cache, ctx);
raw_spin_unlock_irqrestore(&cpu_mmid_lock, flags);
}
/*
* Invalidate the local TLB if needed. Note that we must only clear our
* bit in tlb_flush_pending after this is complete, so that the
* cpu_has_shared_ftlb_entries case below isn't misled.
*/
if (cpumask_test_cpu(cpu, &tlb_flush_pending)) {
if (cpu_has_vtag_icache)
flush_icache_all();
local_flush_tlb_all();
cpumask_clear_cpu(cpu, &tlb_flush_pending);
}
write_c0_memorymapid(ctx & cpu_asid_mask(&boot_cpu_data));
/*
* If this CPU shares FTLB entries with its siblings and one or more of
* those siblings hasn't yet invalidated its TLB following a version
* increase then we need to invalidate any TLB entries for our MMID
* that we might otherwise pick up from a sibling.
*
* We ifdef on CONFIG_SMP because cpu_sibling_map isn't defined in
* CONFIG_SMP=n kernels.
*/
#ifdef CONFIG_SMP
if (cpu_has_shared_ftlb_entries &&
cpumask_intersects(&tlb_flush_pending, &cpu_sibling_map[cpu])) {
/* Ensure we operate on the new MMID */
mtc0_tlbw_hazard();
/*
* Invalidate all TLB entries associated with the new
* MMID, and wait for the invalidation to complete.
*/
ginvt_mmid();
sync_ginv();
}
#endif
setup_pgd:
TLBMISS_HANDLER_SETUP_PGD(mm->pgd);
}
EXPORT_SYMBOL_GPL(check_switch_mmu_context);
static int mmid_init(void)
{
if (!cpu_has_mmid)
return 0;
/*
* Expect allocation after rollover to fail if we don't have at least
* one more MMID than CPUs.
*/
num_mmids = asid_first_version(0);
WARN_ON(num_mmids <= num_possible_cpus());
atomic64_set(&mmid_version, asid_first_version(0));
mmid_map = bitmap_zalloc(num_mmids, GFP_KERNEL);
if (!mmid_map)
panic("Failed to allocate bitmap for %u MMIDs\n", num_mmids);
/* Reserve an MMID for kmap/wired entries */
__set_bit(MMID_KERNEL_WIRED, mmid_map);
pr_info("MMID allocator initialised with %u entries\n", num_mmids);
return 0;
}
early_initcall(mmid_init);
+144
View File
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000 Ani Joshi <ajoshi@unixbox.com>
* Copyright (C) 2000, 2001, 06 Ralf Baechle <ralf@linux-mips.org>
* swiped from i386, and cloned for MIPS by Geert, polished by Ralf.
*/
#include <linux/dma-direct.h>
#include <linux/dma-map-ops.h>
#include <linux/highmem.h>
#include <asm/cache.h>
#include <asm/cpu-type.h>
#include <asm/io.h>
/*
* The affected CPUs below in 'cpu_needs_post_dma_flush()' can speculatively
* fill random cachelines with stale data at any time, requiring an extra
* flush post-DMA.
*
* Warning on the terminology - Linux calls an uncached area coherent; MIPS
* terminology calls memory areas with hardware maintained coherency coherent.
*
* Note that the R14000 and R16000 should also be checked for in this condition.
* However this function is only called on non-I/O-coherent systems and only the
* R10000 and R12000 are used in such systems, the SGI IP28 Indigo² rsp.
* SGI IP32 aka O2.
*/
static inline bool cpu_needs_post_dma_flush(void)
{
switch (boot_cpu_type()) {
case CPU_R10000:
case CPU_R12000:
case CPU_BMIPS5000:
case CPU_LOONGSON2EF:
case CPU_XBURST:
return true;
default:
/*
* Presence of MAARs suggests that the CPU supports
* speculatively prefetching data, and therefore requires
* the post-DMA flush/invalidate.
*/
return cpu_has_maar;
}
}
void arch_dma_prep_coherent(struct page *page, size_t size)
{
dma_cache_wback_inv((unsigned long)page_address(page), size);
}
void *arch_dma_set_uncached(void *addr, size_t size)
{
return (void *)(__pa(addr) + UNCAC_BASE);
}
static inline void dma_sync_virt_for_device(void *addr, size_t size,
enum dma_data_direction dir)
{
switch (dir) {
case DMA_TO_DEVICE:
dma_cache_wback((unsigned long)addr, size);
break;
case DMA_FROM_DEVICE:
dma_cache_inv((unsigned long)addr, size);
break;
case DMA_BIDIRECTIONAL:
dma_cache_wback_inv((unsigned long)addr, size);
break;
default:
BUG();
}
}
static inline void dma_sync_virt_for_cpu(void *addr, size_t size,
enum dma_data_direction dir)
{
switch (dir) {
case DMA_TO_DEVICE:
break;
case DMA_FROM_DEVICE:
case DMA_BIDIRECTIONAL:
dma_cache_inv((unsigned long)addr, size);
break;
default:
BUG();
}
}
/*
* A single sg entry may refer to multiple physically contiguous pages. But
* we still need to process highmem pages individually. If highmem is not
* configured then the bulk of this loop gets optimized out.
*/
static inline void dma_sync_phys(phys_addr_t paddr, size_t size,
enum dma_data_direction dir, bool for_device)
{
struct page *page = pfn_to_page(paddr >> PAGE_SHIFT);
unsigned long offset = paddr & ~PAGE_MASK;
size_t left = size;
do {
size_t len = left;
void *addr;
if (PageHighMem(page)) {
if (offset + len > PAGE_SIZE)
len = PAGE_SIZE - offset;
}
addr = kmap_atomic(page);
if (for_device)
dma_sync_virt_for_device(addr + offset, len, dir);
else
dma_sync_virt_for_cpu(addr + offset, len, dir);
kunmap_atomic(addr);
offset = 0;
page++;
left -= len;
} while (left);
}
void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,
enum dma_data_direction dir)
{
dma_sync_phys(paddr, size, dir, true);
}
#ifdef CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU
void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
enum dma_data_direction dir)
{
if (cpu_needs_post_dma_flush())
dma_sync_phys(paddr, size, dir, false);
}
#endif
#ifdef CONFIG_ARCH_HAS_SETUP_DMA_OPS
void arch_setup_dma_ops(struct device *dev, bool coherent)
{
dev_assign_dma_coherent(dev, coherent);
}
#endif
+25
View File
@@ -0,0 +1,25 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1997, 99, 2001 - 2004 Ralf Baechle <ralf@linux-mips.org>
*/
#include <linux/extable.h>
#include <linux/spinlock.h>
#include <asm/branch.h>
#include <linux/uaccess.h>
int fixup_exception(struct pt_regs *regs)
{
const struct exception_table_entry *fixup;
fixup = search_exception_tables(exception_epc(regs));
if (fixup) {
regs->cp0_epc = fixup->nextinsn;
return 1;
}
return 0;
}
+333
View File
@@ -0,0 +1,333 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1995 - 2000 by Ralf Baechle
*/
#include <linux/context_tracking.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#include <linux/ratelimit.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/kprobes.h>
#include <linux/perf_event.h>
#include <linux/uaccess.h>
#include <asm/branch.h>
#include <asm/mmu_context.h>
#include <asm/ptrace.h>
#include <asm/highmem.h> /* For VMALLOC_END */
#include <asm/traps.h>
#include <linux/kdebug.h>
int show_unhandled_signals = 1;
/*
* This routine handles page faults. It determines the address,
* and the problem, and then passes it off to one of the appropriate
* routines.
*/
static void __do_page_fault(struct pt_regs *regs, unsigned long write,
unsigned long address)
{
struct vm_area_struct * vma = NULL;
struct task_struct *tsk = current;
struct mm_struct *mm = tsk->mm;
const int field = sizeof(unsigned long) * 2;
int si_code;
vm_fault_t fault;
unsigned int flags = FAULT_FLAG_DEFAULT;
static DEFINE_RATELIMIT_STATE(ratelimit_state, 5 * HZ, 10);
#if 0
printk("Cpu%d[%s:%d:%0*lx:%ld:%0*lx]\n", raw_smp_processor_id(),
current->comm, current->pid, field, address, write,
field, regs->cp0_epc);
#endif
#ifdef CONFIG_KPROBES
/*
* This is to notify the fault handler of the kprobes.
*/
if (notify_die(DIE_PAGE_FAULT, "page fault", regs, -1,
current->thread.trap_nr, SIGSEGV) == NOTIFY_STOP)
return;
#endif
si_code = SEGV_MAPERR;
/*
* We fault-in kernel-space virtual memory on-demand. The
* 'reference' page table is init_mm.pgd.
*
* NOTE! We MUST NOT take any locks for this case. We may
* be in an interrupt or a critical region, and should
* only copy the information from the master page table,
* nothing more.
*/
#ifdef CONFIG_64BIT
# define VMALLOC_FAULT_TARGET no_context
#else
# define VMALLOC_FAULT_TARGET vmalloc_fault
#endif
if (unlikely(address >= VMALLOC_START && address <= VMALLOC_END))
goto VMALLOC_FAULT_TARGET;
#ifdef MODULES_VADDR
if (unlikely(address >= MODULES_VADDR && address < MODULES_END))
goto VMALLOC_FAULT_TARGET;
#endif
/*
* If we're in an interrupt or have no user
* context, we must not take the fault..
*/
if (faulthandler_disabled() || !mm)
goto bad_area_nosemaphore;
if (user_mode(regs))
flags |= FAULT_FLAG_USER;
perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, address);
retry:
vma = lock_mm_and_find_vma(mm, address, regs);
if (!vma)
goto bad_area_nosemaphore;
/*
* Ok, we have a good vm_area for this memory access, so
* we can handle it..
*/
si_code = SEGV_ACCERR;
if (write) {
if (!(vma->vm_flags & VM_WRITE))
goto bad_area;
flags |= FAULT_FLAG_WRITE;
} else {
if (cpu_has_rixi) {
if (address == regs->cp0_epc && !(vma->vm_flags & VM_EXEC)) {
#if 0
pr_notice("Cpu%d[%s:%d:%0*lx:%ld:%0*lx] XI violation\n",
raw_smp_processor_id(),
current->comm, current->pid,
field, address, write,
field, regs->cp0_epc);
#endif
goto bad_area;
}
if (!(vma->vm_flags & VM_READ) &&
exception_epc(regs) != address) {
#if 0
pr_notice("Cpu%d[%s:%d:%0*lx:%ld:%0*lx] RI violation\n",
raw_smp_processor_id(),
current->comm, current->pid,
field, address, write,
field, regs->cp0_epc);
#endif
goto bad_area;
}
} else {
if (unlikely(!vma_is_accessible(vma)))
goto bad_area;
}
}
/*
* If for any reason at all we couldn't handle the fault,
* make sure we exit gracefully rather than endlessly redo
* the fault.
*/
fault = handle_mm_fault(vma, address, flags, regs);
if (fault_signal_pending(fault, regs)) {
if (!user_mode(regs))
goto no_context;
return;
}
/* The fault is fully completed (including releasing mmap lock) */
if (fault & VM_FAULT_COMPLETED)
return;
if (unlikely(fault & VM_FAULT_ERROR)) {
if (fault & VM_FAULT_OOM)
goto out_of_memory;
else if (fault & VM_FAULT_SIGSEGV)
goto bad_area;
else if (fault & VM_FAULT_SIGBUS)
goto do_sigbus;
BUG();
}
if (fault & VM_FAULT_RETRY) {
flags |= FAULT_FLAG_TRIED;
/*
* No need to mmap_read_unlock(mm) as we would
* have already released it in __lock_page_or_retry
* in mm/filemap.c.
*/
goto retry;
}
mmap_read_unlock(mm);
return;
/*
* Something tried to access memory that isn't in our memory map..
* Fix it, but check if it's kernel or user first..
*/
bad_area:
mmap_read_unlock(mm);
bad_area_nosemaphore:
/* User mode accesses just cause a SIGSEGV */
if (user_mode(regs)) {
tsk->thread.cp0_badvaddr = address;
tsk->thread.error_code = write;
if (show_unhandled_signals &&
unhandled_signal(tsk, SIGSEGV) &&
__ratelimit(&ratelimit_state)) {
pr_info("do_page_fault(): sending SIGSEGV to %s for invalid %s %0*lx\n",
tsk->comm,
write ? "write access to" : "read access from",
field, address);
pr_info("epc = %0*lx in", field,
(unsigned long) regs->cp0_epc);
print_vma_addr(KERN_CONT " ", regs->cp0_epc);
pr_cont("\n");
pr_info("ra = %0*lx in", field,
(unsigned long) regs->regs[31]);
print_vma_addr(KERN_CONT " ", regs->regs[31]);
pr_cont("\n");
}
current->thread.trap_nr = (regs->cp0_cause >> 2) & 0x1f;
force_sig_fault(SIGSEGV, si_code, (void __user *)address);
return;
}
no_context:
/* Are we prepared to handle this kernel fault? */
if (fixup_exception(regs)) {
current->thread.cp0_baduaddr = address;
return;
}
/*
* Oops. The kernel tried to access some bad page. We'll have to
* terminate things with extreme prejudice.
*/
bust_spinlocks(1);
printk(KERN_ALERT "CPU %d Unable to handle kernel paging request at "
"virtual address %0*lx, epc == %0*lx, ra == %0*lx\n",
raw_smp_processor_id(), field, address, field, regs->cp0_epc,
field, regs->regs[31]);
die("Oops", regs);
out_of_memory:
/*
* We ran out of memory, call the OOM killer, and return the userspace
* (which will retry the fault, or kill us if we got oom-killed).
*/
mmap_read_unlock(mm);
if (!user_mode(regs))
goto no_context;
pagefault_out_of_memory();
return;
do_sigbus:
mmap_read_unlock(mm);
/* Kernel mode? Handle exceptions or die */
if (!user_mode(regs))
goto no_context;
/*
* Send a sigbus, regardless of whether we were in kernel
* or user mode.
*/
#if 0
printk("do_page_fault() #3: sending SIGBUS to %s for "
"invalid %s\n%0*lx (epc == %0*lx, ra == %0*lx)\n",
tsk->comm,
write ? "write access to" : "read access from",
field, address,
field, (unsigned long) regs->cp0_epc,
field, (unsigned long) regs->regs[31]);
#endif
current->thread.trap_nr = (regs->cp0_cause >> 2) & 0x1f;
tsk->thread.cp0_badvaddr = address;
force_sig_fault(SIGBUS, BUS_ADRERR, (void __user *)address);
return;
#ifndef CONFIG_64BIT
vmalloc_fault:
{
/*
* Synchronize this task's top level page-table
* with the 'reference' page table.
*
* Do _not_ use "tsk" here. We might be inside
* an interrupt in the middle of a task switch..
*/
int offset = pgd_index(address);
pgd_t *pgd, *pgd_k;
p4d_t *p4d, *p4d_k;
pud_t *pud, *pud_k;
pmd_t *pmd, *pmd_k;
pte_t *pte_k;
pgd = (pgd_t *) pgd_current[raw_smp_processor_id()] + offset;
pgd_k = init_mm.pgd + offset;
if (!pgd_present(*pgd_k))
goto no_context;
set_pgd(pgd, *pgd_k);
p4d = p4d_offset(pgd, address);
p4d_k = p4d_offset(pgd_k, address);
if (!p4d_present(*p4d_k))
goto no_context;
pud = pud_offset(p4d, address);
pud_k = pud_offset(p4d_k, address);
if (!pud_present(*pud_k))
goto no_context;
pmd = pmd_offset(pud, address);
pmd_k = pmd_offset(pud_k, address);
if (!pmd_present(*pmd_k))
goto no_context;
set_pmd(pmd, *pmd_k);
pte_k = pte_offset_kernel(pmd_k, address);
if (!pte_present(*pte_k))
goto no_context;
return;
}
#endif
}
NOKPROBE_SYMBOL(__do_page_fault);
asmlinkage void do_page_fault(struct pt_regs *regs,
unsigned long write, unsigned long address)
{
enum ctx_state prev_state;
prev_state = exception_enter();
__do_page_fault(regs, write, address);
exception_exit(prev_state);
}
NOKPROBE_SYMBOL(do_page_fault);
+17
View File
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/init.h>
#include <linux/export.h>
#include <linux/highmem.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <asm/fixmap.h>
#include <asm/tlbflush.h>
unsigned long highstart_pfn, highend_pfn;
void kmap_flush_tlb(unsigned long addr)
{
flush_tlb_one(addr);
}
EXPORT_SYMBOL(kmap_flush_tlb);
+59
View File
@@ -0,0 +1,59 @@
/*
* MIPS Huge TLB Page Support for Kernel.
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2002, Rohit Seth <rohit.seth@intel.com>
* Copyright 2005, Embedded Alley Solutions, Inc.
* Matt Porter <mporter@embeddedalley.com>
* Copyright (C) 2008, 2009 Cavium Networks, Inc.
*/
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/hugetlb.h>
#include <linux/pagemap.h>
#include <linux/err.h>
#include <linux/sysctl.h>
#include <asm/mman.h>
#include <asm/tlb.h>
#include <asm/tlbflush.h>
pte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, unsigned long sz)
{
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pte_t *pte = NULL;
pgd = pgd_offset(mm, addr);
p4d = p4d_alloc(mm, pgd, addr);
pud = pud_alloc(mm, p4d, addr);
if (pud)
pte = (pte_t *)pmd_alloc(mm, pud, addr);
return pte;
}
pte_t *huge_pte_offset(struct mm_struct *mm, unsigned long addr,
unsigned long sz)
{
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd = NULL;
pgd = pgd_offset(mm, addr);
if (pgd_present(*pgd)) {
p4d = p4d_offset(pgd, addr);
if (p4d_present(*p4d)) {
pud = pud_offset(p4d, addr);
if (pud_present(*pud))
pmd = pmd_offset(pud, addr);
}
}
return (pte_t *) pmd;
}
+581
View File
@@ -0,0 +1,581 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1994 - 2000 Ralf Baechle
* Copyright (C) 1999, 2000 Silicon Graphics, Inc.
* Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com
* Copyright (C) 2000 MIPS Technologies, Inc. All rights reserved.
*/
#include <linux/bug.h>
#include <linux/init.h>
#include <linux/export.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/pagemap.h>
#include <linux/ptrace.h>
#include <linux/mman.h>
#include <linux/mm.h>
#include <linux/memblock.h>
#include <linux/highmem.h>
#include <linux/swap.h>
#include <linux/proc_fs.h>
#include <linux/pfn.h>
#include <linux/hardirq.h>
#include <linux/gfp.h>
#include <linux/kcore.h>
#include <linux/initrd.h>
#include <linux/execmem.h>
#include <asm/bootinfo.h>
#include <asm/cachectl.h>
#include <asm/cpu.h>
#include <asm/dma.h>
#include <asm/maar.h>
#include <asm/mmu_context.h>
#include <asm/mmzone.h>
#include <asm/sections.h>
#include <asm/pgalloc.h>
#include <asm/tlb.h>
#include <asm/fixmap.h>
/*
* We have up to 8 empty zeroed pages so we can map one of the right colour
* when needed. This is necessary only on R4000 / R4400 SC and MC versions
* where we have to avoid VCED / VECI exceptions for good performance at
* any price. Since page is never written to after the initialization we
* don't have to care about aliases on other CPUs.
*/
unsigned long empty_zero_page, zero_page_mask;
EXPORT_SYMBOL_GPL(empty_zero_page);
EXPORT_SYMBOL(zero_page_mask);
void __init arch_setup_zero_pages(void)
{
unsigned int order;
if (cpu_has_vce)
order = 3;
else
order = 0;
empty_zero_page = (unsigned long)memblock_alloc_or_panic(PAGE_SIZE << order, PAGE_SIZE);
zero_page_mask = ((PAGE_SIZE << order) - 1) & PAGE_MASK;
}
static void *__kmap_pgprot(struct page *page, unsigned long addr, pgprot_t prot)
{
enum fixed_addresses idx;
unsigned int old_mmid;
unsigned long vaddr, flags, entrylo;
unsigned long old_ctx;
pte_t pte;
int tlbidx;
BUG_ON(folio_test_dcache_dirty(page_folio(page)));
preempt_disable();
pagefault_disable();
idx = (addr >> PAGE_SHIFT) & (FIX_N_COLOURS - 1);
idx += in_interrupt() ? FIX_N_COLOURS : 0;
vaddr = __fix_to_virt(FIX_CMAP_END - idx);
pte = mk_pte(page, prot);
#if defined(CONFIG_XPA)
entrylo = pte_to_entrylo(pte.pte_high);
#elif defined(CONFIG_PHYS_ADDR_T_64BIT) && defined(CONFIG_CPU_MIPS32)
entrylo = pte.pte_high;
#else
entrylo = pte_to_entrylo(pte_val(pte));
#endif
local_irq_save(flags);
old_ctx = read_c0_entryhi();
write_c0_entryhi(vaddr & (PAGE_MASK << 1));
write_c0_entrylo0(entrylo);
write_c0_entrylo1(entrylo);
if (cpu_has_mmid) {
old_mmid = read_c0_memorymapid();
write_c0_memorymapid(MMID_KERNEL_WIRED);
}
#ifdef CONFIG_XPA
if (cpu_has_xpa) {
entrylo = (pte.pte_low & _PFNX_MASK);
writex_c0_entrylo0(entrylo);
writex_c0_entrylo1(entrylo);
}
#endif
tlbidx = num_wired_entries();
write_c0_wired(tlbidx + 1);
write_c0_index(tlbidx);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
write_c0_entryhi(old_ctx);
if (cpu_has_mmid)
write_c0_memorymapid(old_mmid);
local_irq_restore(flags);
return (void*) vaddr;
}
void *kmap_coherent(struct page *page, unsigned long addr)
{
return __kmap_pgprot(page, addr, PAGE_KERNEL);
}
void *kmap_noncoherent(struct page *page, unsigned long addr)
{
return __kmap_pgprot(page, addr, PAGE_KERNEL_NC);
}
void kunmap_coherent(void)
{
unsigned int wired;
unsigned long flags, old_ctx;
local_irq_save(flags);
old_ctx = read_c0_entryhi();
wired = num_wired_entries() - 1;
write_c0_wired(wired);
write_c0_index(wired);
write_c0_entryhi(UNIQUE_ENTRYHI(wired));
write_c0_entrylo0(0);
write_c0_entrylo1(0);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
write_c0_entryhi(old_ctx);
local_irq_restore(flags);
pagefault_enable();
preempt_enable();
}
void copy_user_highpage(struct page *to, struct page *from,
unsigned long vaddr, struct vm_area_struct *vma)
{
struct folio *src = page_folio(from);
void *vfrom, *vto;
vto = kmap_atomic(to);
if (cpu_has_dc_aliases &&
folio_mapped(src) && !folio_test_dcache_dirty(src)) {
vfrom = kmap_coherent(from, vaddr);
copy_page(vto, vfrom);
kunmap_coherent();
} else {
vfrom = kmap_atomic(from);
copy_page(vto, vfrom);
kunmap_atomic(vfrom);
}
if ((!cpu_has_ic_fills_f_dc) ||
pages_do_alias((unsigned long)vto, vaddr & PAGE_MASK))
flush_data_cache_page((unsigned long)vto);
kunmap_atomic(vto);
/* Make sure this page is cleared on other CPU's too before using it */
smp_wmb();
}
void copy_to_user_page(struct vm_area_struct *vma,
struct page *page, unsigned long vaddr, void *dst, const void *src,
unsigned long len)
{
struct folio *folio = page_folio(page);
if (cpu_has_dc_aliases &&
folio_mapped(folio) && !folio_test_dcache_dirty(folio)) {
void *vto = kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK);
memcpy(vto, src, len);
kunmap_coherent();
} else {
memcpy(dst, src, len);
if (cpu_has_dc_aliases)
folio_set_dcache_dirty(folio);
}
if (vma->vm_flags & VM_EXEC)
flush_cache_page(vma, vaddr, page_to_pfn(page));
}
void copy_from_user_page(struct vm_area_struct *vma,
struct page *page, unsigned long vaddr, void *dst, const void *src,
unsigned long len)
{
struct folio *folio = page_folio(page);
if (cpu_has_dc_aliases &&
folio_mapped(folio) && !folio_test_dcache_dirty(folio)) {
void *vfrom = kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK);
memcpy(dst, vfrom, len);
kunmap_coherent();
} else {
memcpy(dst, src, len);
if (cpu_has_dc_aliases)
folio_set_dcache_dirty(folio);
}
}
EXPORT_SYMBOL_GPL(copy_from_user_page);
void __init fixrange_init(unsigned long start, unsigned long end,
pgd_t *pgd_base)
{
#ifdef CONFIG_HIGHMEM
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
int i, j, k;
unsigned long vaddr;
vaddr = start;
i = pgd_index(vaddr);
j = pud_index(vaddr);
k = pmd_index(vaddr);
pgd = pgd_base + i;
for ( ; (i < PTRS_PER_PGD) && (vaddr < end); pgd++, i++) {
pud = (pud_t *)pgd;
for ( ; (j < PTRS_PER_PUD) && (vaddr < end); pud++, j++) {
pmd = (pmd_t *)pud;
for (; (k < PTRS_PER_PMD) && (vaddr < end); pmd++, k++) {
if (pmd_none(*pmd)) {
pte = (pte_t *) memblock_alloc_low(PAGE_SIZE,
PAGE_SIZE);
if (!pte)
panic("%s: Failed to allocate %lu bytes align=%lx\n",
__func__, PAGE_SIZE,
PAGE_SIZE);
set_pmd(pmd, __pmd((unsigned long)pte));
BUG_ON(pte != pte_offset_kernel(pmd, 0));
}
vaddr += PMD_SIZE;
}
k = 0;
}
j = 0;
}
#endif
}
struct maar_walk_info {
struct maar_config cfg[16];
unsigned int num_cfg;
};
static int maar_res_walk(unsigned long start_pfn, unsigned long nr_pages,
void *data)
{
struct maar_walk_info *wi = data;
struct maar_config *cfg;
unsigned int maar_align;
/* Ensure we don't overflow the cfg array */
if (WARN_ON(wi->num_cfg >= ARRAY_SIZE(wi->cfg)))
return -1;
cfg = &wi->cfg[wi->num_cfg];
/* MAAR registers hold physical addresses right shifted by 4 bits */
maar_align = BIT(MIPS_MAAR_ADDR_SHIFT + 4);
/* Fill in the MAAR config entry */
cfg->lower = ALIGN(PFN_PHYS(start_pfn), maar_align);
cfg->upper = ALIGN_DOWN(PFN_PHYS(start_pfn + nr_pages), maar_align) - 1;
cfg->attrs = MIPS_MAAR_S;
wi->num_cfg++;
return 0;
}
unsigned __weak platform_maar_init(unsigned num_pairs)
{
unsigned int num_configured;
struct maar_walk_info wi;
wi.num_cfg = 0;
walk_system_ram_range(0, max_pfn, &wi, maar_res_walk);
num_configured = maar_config(wi.cfg, wi.num_cfg, num_pairs);
if (num_configured < wi.num_cfg)
pr_warn("Not enough MAAR pairs (%u) for all memory regions (%u)\n",
num_pairs, wi.num_cfg);
return num_configured;
}
void maar_init(void)
{
unsigned num_maars, used, i;
phys_addr_t lower, upper, attr;
static struct {
struct maar_config cfgs[3];
unsigned used;
} recorded = { { { 0 } }, 0 };
if (!cpu_has_maar)
return;
/* Detect the number of MAARs */
write_c0_maari(~0);
back_to_back_c0_hazard();
num_maars = read_c0_maari() + 1;
/* MAARs should be in pairs */
WARN_ON(num_maars % 2);
/* Set MAARs using values we recorded already */
if (recorded.used) {
used = maar_config(recorded.cfgs, recorded.used, num_maars / 2);
BUG_ON(used != recorded.used);
} else {
/* Configure the required MAARs */
used = platform_maar_init(num_maars / 2);
}
/* Disable any further MAARs */
for (i = (used * 2); i < num_maars; i++) {
write_c0_maari(i);
back_to_back_c0_hazard();
write_c0_maar(0);
back_to_back_c0_hazard();
}
if (recorded.used)
return;
pr_info("MAAR configuration:\n");
for (i = 0; i < num_maars; i += 2) {
write_c0_maari(i);
back_to_back_c0_hazard();
upper = read_c0_maar();
#ifdef CONFIG_XPA
upper |= (phys_addr_t)readx_c0_maar() << MIPS_MAARX_ADDR_SHIFT;
#endif
write_c0_maari(i + 1);
back_to_back_c0_hazard();
lower = read_c0_maar();
#ifdef CONFIG_XPA
lower |= (phys_addr_t)readx_c0_maar() << MIPS_MAARX_ADDR_SHIFT;
#endif
attr = lower & upper;
lower = (lower & MIPS_MAAR_ADDR) << 4;
upper = ((upper & MIPS_MAAR_ADDR) << 4) | 0xffff;
pr_info(" [%d]: ", i / 2);
if ((attr & MIPS_MAAR_V) != MIPS_MAAR_V) {
pr_cont("disabled\n");
continue;
}
pr_cont("%pa-%pa", &lower, &upper);
if (attr & MIPS_MAAR_S)
pr_cont(" speculate");
pr_cont("\n");
/* Record the setup for use on secondary CPUs */
if (used <= ARRAY_SIZE(recorded.cfgs)) {
recorded.cfgs[recorded.used].lower = lower;
recorded.cfgs[recorded.used].upper = upper;
recorded.cfgs[recorded.used].attrs = attr;
recorded.used++;
}
}
}
#ifndef CONFIG_NUMA
void __init arch_zone_limits_init(unsigned long *max_zone_pfns)
{
#ifdef CONFIG_ZONE_DMA
max_zone_pfns[ZONE_DMA] = MAX_DMA_PFN;
#endif
#ifdef CONFIG_ZONE_DMA32
max_zone_pfns[ZONE_DMA32] = MAX_DMA32_PFN;
#endif
max_zone_pfns[ZONE_NORMAL] = max_low_pfn;
#ifdef CONFIG_HIGHMEM
max_zone_pfns[ZONE_HIGHMEM] = highend_pfn;
if (cpu_has_dc_aliases && max_low_pfn != highend_pfn) {
printk(KERN_WARNING "This processor doesn't support highmem."
" %ldk highmem ignored\n",
(highend_pfn - max_low_pfn) << (PAGE_SHIFT - 10));
max_zone_pfns[ZONE_HIGHMEM] = max_low_pfn;
}
#endif
}
#ifdef CONFIG_64BIT
static struct kcore_list kcore_kseg0;
#endif
static inline void __init highmem_init(void)
{
#ifdef CONFIG_HIGHMEM
unsigned long tmp;
/*
* If CPU cannot support HIGHMEM discard any memory above highstart_pfn
*/
if (cpu_has_dc_aliases) {
if (highstart_pfn)
memblock_remove(PFN_PHYS(highstart_pfn), -1);
return;
}
for (tmp = highstart_pfn; tmp < highend_pfn; tmp++) {
struct page *page = pfn_to_page(tmp);
if (!memblock_is_memory(PFN_PHYS(tmp)))
SetPageReserved(page);
}
#endif
}
void __init arch_mm_preinit(void)
{
/*
* When PFN_PTE_SHIFT is greater than PAGE_SHIFT we won't have enough PTE
* bits to hold a full 32b physical address on MIPS32 systems.
*/
BUILD_BUG_ON(IS_ENABLED(CONFIG_32BIT) && (PFN_PTE_SHIFT > PAGE_SHIFT));
maar_init();
highmem_init();
#ifdef CONFIG_64BIT
if ((unsigned long) &_text > (unsigned long) CKSEG0)
/* The -4 is a hack so that user tools don't have to handle
the overflow. */
kclist_add(&kcore_kseg0, (void *) CKSEG0,
0x80000000 - 4, KCORE_TEXT);
#endif
}
#endif /* !CONFIG_NUMA */
void free_init_pages(const char *what, unsigned long begin, unsigned long end)
{
unsigned long pfn;
for (pfn = PFN_UP(begin); pfn < PFN_DOWN(end); pfn++) {
struct page *page = pfn_to_page(pfn);
void *addr = phys_to_virt(PFN_PHYS(pfn));
memset(addr, POISON_FREE_INITMEM, PAGE_SIZE);
free_reserved_page(page);
}
printk(KERN_INFO "Freeing %s: %ldk freed\n", what, (end - begin) >> 10);
}
void (*free_init_pages_eva)(void *begin, void *end) = NULL;
void __weak __init prom_free_prom_memory(void)
{
/* nothing to do */
}
void __ref free_initmem(void)
{
prom_free_prom_memory();
/*
* Let the platform define a specific function to free the
* init section since EVA may have used any possible mapping
* between virtual and physical addresses.
*/
if (free_init_pages_eva)
free_init_pages_eva((void *)&__init_begin, (void *)&__init_end);
else
free_initmem_default(POISON_FREE_INITMEM);
}
#ifdef CONFIG_HAVE_SETUP_PER_CPU_AREA
unsigned long __per_cpu_offset[NR_CPUS] __read_mostly;
EXPORT_SYMBOL(__per_cpu_offset);
static int __init pcpu_cpu_distance(unsigned int from, unsigned int to)
{
return node_distance(cpu_to_node(from), cpu_to_node(to));
}
static int __init pcpu_cpu_to_node(int cpu)
{
return cpu_to_node(cpu);
}
void __init setup_per_cpu_areas(void)
{
unsigned long delta;
unsigned int cpu;
int rc;
/*
* Always reserve area for module percpu variables. That's
* what the legacy allocator did.
*/
rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,
PERCPU_DYNAMIC_RESERVE, PAGE_SIZE,
pcpu_cpu_distance,
pcpu_cpu_to_node);
if (rc < 0)
panic("Failed to initialize percpu areas.");
delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;
for_each_possible_cpu(cpu)
__per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu];
}
#endif
#ifndef CONFIG_MIPS_PGD_C0_CONTEXT
unsigned long pgd_current[NR_CPUS];
#endif
/*
* Align swapper_pg_dir in to 64K, allows its address to be loaded
* with a single LUI instruction in the TLB handlers. If we used
* __aligned(64K), its size would get rounded up to the alignment
* size, and waste space. So we place it in its own section and align
* it in the linker script.
*/
pgd_t swapper_pg_dir[PTRS_PER_PGD] __section(".bss..swapper_pg_dir");
#ifndef __PAGETABLE_PUD_FOLDED
pud_t invalid_pud_table[PTRS_PER_PUD] __page_aligned_bss;
#endif
#ifndef __PAGETABLE_PMD_FOLDED
pmd_t invalid_pmd_table[PTRS_PER_PMD] __page_aligned_bss;
EXPORT_SYMBOL_GPL(invalid_pmd_table);
#endif
pte_t invalid_pte_table[PTRS_PER_PTE] __page_aligned_bss;
EXPORT_SYMBOL(invalid_pte_table);
#ifdef CONFIG_EXECMEM
#ifdef MODULES_VADDR
static struct execmem_info execmem_info __ro_after_init;
struct execmem_info __init *execmem_arch_setup(void)
{
execmem_info = (struct execmem_info){
.ranges = {
[EXECMEM_DEFAULT] = {
.start = MODULES_VADDR,
.end = MODULES_END,
.pgprot = PAGE_KERNEL,
.alignment = 1,
},
},
};
return &execmem_info;
}
#endif
#endif /* CONFIG_EXECMEM */
+123
View File
@@ -0,0 +1,123 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* (C) Copyright 1995 1996 Linus Torvalds
* (C) Copyright 2001, 2002 Ralf Baechle
*/
#include <linux/export.h>
#include <asm/addrspace.h>
#include <asm/byteorder.h>
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm_types.h>
#include <linux/io.h>
#include <asm/cacheflush.h>
#include <asm/tlbflush.h>
#include <ioremap.h>
#define IS_LOW512(addr) (!((phys_addr_t)(addr) & (phys_addr_t) ~0x1fffffffULL))
#define IS_KSEG1(addr) (((unsigned long)(addr) & ~0x1fffffffUL) == CKSEG1)
static int __ioremap_check_ram(unsigned long start_pfn, unsigned long nr_pages,
void *arg)
{
unsigned long i;
for (i = 0; i < nr_pages; i++) {
if (pfn_valid(start_pfn + i) &&
!PageReserved(pfn_to_page(start_pfn + i)))
return 1;
}
return 0;
}
/*
* ioremap_prot - map bus memory into CPU space
* @phys_addr: bus address of the memory
* @size: size of the resource to map
*
* ioremap_prot gives the caller control over cache coherency attributes (CCA)
*/
void __iomem *ioremap_prot(phys_addr_t phys_addr, unsigned long size,
pgprot_t prot)
{
unsigned long flags = pgprot_val(prot) & _CACHE_MASK;
unsigned long offset, pfn, last_pfn;
struct vm_struct *area;
phys_addr_t last_addr;
unsigned long vaddr;
void __iomem *cpu_addr;
cpu_addr = plat_ioremap(phys_addr, size, flags);
if (cpu_addr)
return cpu_addr;
phys_addr = fixup_bigphys_addr(phys_addr, size);
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr)
return NULL;
/*
* Map uncached objects in the low 512mb of address space using KSEG1,
* otherwise map using page tables.
*/
if (IS_LOW512(phys_addr) && IS_LOW512(last_addr) &&
flags == _CACHE_UNCACHED)
return (void __iomem *) CKSEG1ADDR(phys_addr);
/* Early remaps should use the unmapped regions til' VM is available */
if (WARN_ON_ONCE(!slab_is_available()))
return NULL;
/*
* Don't allow anybody to remap RAM that may be allocated by the page
* allocator, since that could lead to races & data clobbering.
*/
pfn = PFN_DOWN(phys_addr);
last_pfn = PFN_DOWN(last_addr);
if (walk_system_ram_range(pfn, last_pfn - pfn + 1, NULL,
__ioremap_check_ram) == 1) {
WARN_ONCE(1, "ioremap on RAM at %pa - %pa\n",
&phys_addr, &last_addr);
return NULL;
}
/*
* Mappings have to be page-aligned
*/
offset = phys_addr & ~PAGE_MASK;
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(last_addr + 1) - phys_addr;
/*
* Ok, go for it..
*/
area = get_vm_area(size, VM_IOREMAP);
if (!area)
return NULL;
vaddr = (unsigned long)area->addr;
flags |= _PAGE_GLOBAL | _PAGE_PRESENT | __READABLE | __WRITEABLE;
if (ioremap_page_range(vaddr, vaddr + size, phys_addr,
__pgprot(flags))) {
free_vm_area(area);
return NULL;
}
return (void __iomem *)(vaddr + offset);
}
EXPORT_SYMBOL(ioremap_prot);
void iounmap(const volatile void __iomem *addr)
{
if (!plat_iounmap(addr) && !IS_KSEG1(addr))
vunmap((void *)((unsigned long)addr & PAGE_MASK));
}
EXPORT_SYMBOL(iounmap);
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/io.h>
#include <ioremap.h>
void __iomem *ioremap_prot(phys_addr_t offset, unsigned long size,
pgprot_t prot)
{
unsigned long flags = pgprot_val(prot) & _CACHE_MASK;
u64 base = (flags == _CACHE_UNCACHED ? IO_BASE : UNCAC_BASE);
void __iomem *addr;
addr = plat_ioremap(offset, size, flags);
if (!addr)
addr = (void __iomem *)(unsigned long)(base + offset);
return addr;
}
EXPORT_SYMBOL(ioremap_prot);
void iounmap(const volatile void __iomem *addr)
{
plat_iounmap(addr);
}
EXPORT_SYMBOL(iounmap);
+10
View File
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/uaccess.h>
#include <linux/kernel.h>
bool copy_from_kernel_nofault_allowed(const void *unsafe_src, size_t size)
{
/* highest bit set means kernel space */
return (unsigned long)unsafe_src >> (BITS_PER_LONG - 1);
}
+129
View File
@@ -0,0 +1,129 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2011 Wind River Systems,
* written by Ralf Baechle <ralf@linux-mips.org>
*/
#include <linux/compiler.h>
#include <linux/elf-randomize.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/export.h>
#include <linux/personality.h>
#include <linux/random.h>
#include <linux/sched/signal.h>
#include <linux/sched/mm.h>
unsigned long shm_align_mask = PAGE_SIZE - 1; /* Sane caches */
EXPORT_SYMBOL(shm_align_mask);
#define COLOUR_ALIGN(addr, pgoff) \
((((addr) + shm_align_mask) & ~shm_align_mask) + \
(((pgoff) << PAGE_SHIFT) & shm_align_mask))
enum mmap_allocation_direction {UP, DOWN};
static unsigned long arch_get_unmapped_area_common(struct file *filp,
unsigned long addr0, unsigned long len, unsigned long pgoff,
unsigned long flags, enum mmap_allocation_direction dir)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned long addr = addr0;
int do_color_align;
struct vm_unmapped_area_info info = {};
if (unlikely(len > TASK_SIZE))
return -ENOMEM;
if (flags & MAP_FIXED) {
/* Even MAP_FIXED mappings must reside within TASK_SIZE */
if (TASK_SIZE - len < addr)
return -EINVAL;
/*
* We do not accept a shared mapping if it would violate
* cache aliasing constraints.
*/
if ((flags & MAP_SHARED) &&
((addr - (pgoff << PAGE_SHIFT)) & shm_align_mask))
return -EINVAL;
return addr;
}
do_color_align = 0;
if (filp || (flags & MAP_SHARED))
do_color_align = 1;
/* requesting a specific address */
if (addr) {
if (do_color_align)
addr = COLOUR_ALIGN(addr, pgoff);
else
addr = PAGE_ALIGN(addr);
vma = find_vma(mm, addr);
if (TASK_SIZE - len >= addr &&
(!vma || addr + len <= vm_start_gap(vma)))
return addr;
}
info.length = len;
info.align_mask = do_color_align ? (PAGE_MASK & shm_align_mask) : 0;
info.align_offset = pgoff << PAGE_SHIFT;
if (dir == DOWN) {
info.flags = VM_UNMAPPED_AREA_TOPDOWN;
info.low_limit = PAGE_SIZE;
info.high_limit = mm->mmap_base;
addr = vm_unmapped_area(&info);
if (!(addr & ~PAGE_MASK))
return addr;
/*
* A failed mmap() very likely causes application failure,
* so fall back to the bottom-up function here. This scenario
* can happen with large stack limits and large mmap()
* allocations.
*/
}
info.low_limit = mm->mmap_base;
info.high_limit = TASK_SIZE;
return vm_unmapped_area(&info);
}
unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr0,
unsigned long len, unsigned long pgoff, unsigned long flags,
vm_flags_t vm_flags)
{
return arch_get_unmapped_area_common(filp,
addr0, len, pgoff, flags, UP);
}
/*
* There is no need to export this but sched.h declares the function as
* extern so making it static here results in an error.
*/
unsigned long arch_get_unmapped_area_topdown(struct file *filp,
unsigned long addr0, unsigned long len, unsigned long pgoff,
unsigned long flags, vm_flags_t vm_flags)
{
return arch_get_unmapped_area_common(filp,
addr0, len, pgoff, flags, DOWN);
}
bool __virt_addr_valid(const volatile void *kaddr)
{
unsigned long vaddr = (unsigned long)kaddr;
if ((vaddr < PAGE_OFFSET) || (vaddr >= MAP_BASE))
return false;
return pfn_valid(PFN_DOWN(virt_to_phys(kaddr)));
}
EXPORT_SYMBOL_GPL(__virt_addr_valid);
+53
View File
@@ -0,0 +1,53 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Micro-assembler generated clear_page/copy_page functions.
*
* Copyright (C) 2012 MIPS Technologies, Inc.
* Copyright (C) 2012 Ralf Baechle <ralf@linux-mips.org>
*/
#include <linux/export.h>
#include <asm/asm.h>
#include <asm/regdef.h>
#ifdef CONFIG_SIBYTE_DMA_PAGEOPS
#define cpu_clear_page_function_name clear_page_cpu
#define cpu_copy_page_function_name copy_page_cpu
#else
#define cpu_clear_page_function_name clear_page
#define cpu_copy_page_function_name copy_page
#endif
/*
* Maximum sizes:
*
* R4000 128 bytes S-cache: 0x058 bytes
* R4600 v1.7: 0x05c bytes
* R4600 v2.0: 0x060 bytes
* With prefetching, 16 word strides 0x120 bytes
*/
EXPORT(__clear_page_start)
LEAF(cpu_clear_page_function_name)
EXPORT_SYMBOL(cpu_clear_page_function_name)
1: j 1b /* Dummy, will be replaced. */
.space 288
END(cpu_clear_page_function_name)
EXPORT(__clear_page_end)
/*
* Maximum sizes:
*
* R4000 128 bytes S-cache: 0x11c bytes
* R4600 v1.7: 0x080 bytes
* R4600 v2.0: 0x07c bytes
* With prefetching, 16 word strides 0x540 bytes
*/
EXPORT(__copy_page_start)
LEAF(cpu_copy_page_function_name)
EXPORT_SYMBOL(cpu_copy_page_function_name)
1: j 1b /* Dummy, will be replaced. */
.space 1344
END(cpu_copy_page_function_name)
EXPORT(__copy_page_end)
+670
View File
@@ -0,0 +1,670 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2003, 04, 05 Ralf Baechle (ralf@linux-mips.org)
* Copyright (C) 2007 Maciej W. Rozycki
* Copyright (C) 2008 Thiemo Seufer
* Copyright (C) 2012 MIPS Technologies, Inc.
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/mm.h>
#include <linux/proc_fs.h>
#include <asm/bugs.h>
#include <asm/cacheops.h>
#include <asm/cpu-type.h>
#include <asm/inst.h>
#include <asm/io.h>
#include <asm/page.h>
#include <asm/prefetch.h>
#include <asm/bootinfo.h>
#include <asm/mipsregs.h>
#include <asm/mmu_context.h>
#include <asm/regdef.h>
#include <asm/cpu.h>
#ifdef CONFIG_SIBYTE_DMA_PAGEOPS
#include <asm/sibyte/sb1250.h>
#include <asm/sibyte/sb1250_regs.h>
#include <asm/sibyte/sb1250_dma.h>
#endif
#include <asm/uasm.h>
/* Handle labels (which must be positive integers). */
enum label_id {
label_clear_nopref = 1,
label_clear_pref,
label_copy_nopref,
label_copy_pref_both,
label_copy_pref_store,
};
UASM_L_LA(_clear_nopref)
UASM_L_LA(_clear_pref)
UASM_L_LA(_copy_nopref)
UASM_L_LA(_copy_pref_both)
UASM_L_LA(_copy_pref_store)
/* We need one branch and therefore one relocation per target label. */
static struct uasm_label labels[5];
static struct uasm_reloc relocs[5];
#define cpu_is_r4600_v1_x() ((read_c0_prid() & 0xfffffff0) == 0x00002010)
#define cpu_is_r4600_v2_x() ((read_c0_prid() & 0xfffffff0) == 0x00002020)
/*
* R6 has a limited offset of the pref instruction.
* Skip it if the offset is more than 9 bits.
*/
#define _uasm_i_pref(a, b, c, d) \
do { \
if (cpu_has_mips_r6) { \
if (c <= 0xff && c >= -0x100) \
uasm_i_pref(a, b, c, d);\
} else { \
uasm_i_pref(a, b, c, d); \
} \
} while(0)
static int pref_bias_clear_store;
static int pref_bias_copy_load;
static int pref_bias_copy_store;
static u32 pref_src_mode;
static u32 pref_dst_mode;
static int clear_word_size;
static int copy_word_size;
static int half_clear_loop_size;
static int half_copy_loop_size;
static int cache_line_size;
#define cache_line_mask() (cache_line_size - 1)
static inline void
pg_addiu(u32 **buf, unsigned int reg1, unsigned int reg2, unsigned int off)
{
if (cpu_has_64bit_gp_regs &&
IS_ENABLED(CONFIG_CPU_DADDI_WORKAROUNDS) &&
r4k_daddiu_bug()) {
if (off > 0x7fff) {
uasm_i_lui(buf, GPR_T9, uasm_rel_hi(off));
uasm_i_addiu(buf, GPR_T9, GPR_T9, uasm_rel_lo(off));
} else
uasm_i_addiu(buf, GPR_T9, GPR_ZERO, off);
uasm_i_daddu(buf, reg1, reg2, GPR_T9);
} else {
if (off > 0x7fff) {
uasm_i_lui(buf, GPR_T9, uasm_rel_hi(off));
uasm_i_addiu(buf, GPR_T9, GPR_T9, uasm_rel_lo(off));
UASM_i_ADDU(buf, reg1, reg2, GPR_T9);
} else
UASM_i_ADDIU(buf, reg1, reg2, off);
}
}
static void set_prefetch_parameters(void)
{
if (cpu_has_64bit_gp_regs || cpu_has_64bit_zero_reg)
clear_word_size = 8;
else
clear_word_size = 4;
if (cpu_has_64bit_gp_regs)
copy_word_size = 8;
else
copy_word_size = 4;
/*
* The pref's used here are using "streaming" hints, which cause the
* copied data to be kicked out of the cache sooner. A page copy often
* ends up copying a lot more data than is commonly used, so this seems
* to make sense in terms of reducing cache pollution, but I've no real
* performance data to back this up.
*/
if (cpu_has_prefetch) {
/*
* XXX: Most prefetch bias values in here are based on
* guesswork.
*/
cache_line_size = cpu_dcache_line_size();
switch (current_cpu_type()) {
case CPU_R5500:
case CPU_TX49XX:
/* These processors only support the Pref_Load. */
pref_bias_copy_load = 256;
break;
case CPU_R10000:
case CPU_R12000:
case CPU_R14000:
case CPU_R16000:
/*
* Those values have been experimentally tuned for an
* Origin 200.
*/
pref_bias_clear_store = 512;
pref_bias_copy_load = 256;
pref_bias_copy_store = 256;
pref_src_mode = Pref_LoadStreamed;
pref_dst_mode = Pref_StoreStreamed;
break;
case CPU_SB1:
case CPU_SB1A:
pref_bias_clear_store = 128;
pref_bias_copy_load = 128;
pref_bias_copy_store = 128;
/*
* SB1 pass1 Pref_LoadStreamed/Pref_StoreStreamed
* hints are broken.
*/
if (current_cpu_type() == CPU_SB1 &&
(current_cpu_data.processor_id & 0xff) < 0x02) {
pref_src_mode = Pref_Load;
pref_dst_mode = Pref_Store;
} else {
pref_src_mode = Pref_LoadStreamed;
pref_dst_mode = Pref_StoreStreamed;
}
break;
case CPU_LOONGSON64:
/* Loongson-3 only support the Pref_Load/Pref_Store. */
pref_bias_clear_store = 128;
pref_bias_copy_load = 128;
pref_bias_copy_store = 128;
pref_src_mode = Pref_Load;
pref_dst_mode = Pref_Store;
break;
default:
pref_bias_clear_store = 128;
pref_bias_copy_load = 256;
pref_bias_copy_store = 128;
pref_src_mode = Pref_LoadStreamed;
if (cpu_has_mips_r6)
/*
* Bit 30 (Pref_PrepareForStore) has been
* removed from MIPS R6. Use bit 5
* (Pref_StoreStreamed).
*/
pref_dst_mode = Pref_StoreStreamed;
else
pref_dst_mode = Pref_PrepareForStore;
break;
}
} else {
if (cpu_has_cache_cdex_s)
cache_line_size = cpu_scache_line_size();
else if (cpu_has_cache_cdex_p)
cache_line_size = cpu_dcache_line_size();
}
/*
* Too much unrolling will overflow the available space in
* clear_space_array / copy_page_array.
*/
half_clear_loop_size = min(16 * clear_word_size,
max(cache_line_size >> 1,
4 * clear_word_size));
half_copy_loop_size = min(16 * copy_word_size,
max(cache_line_size >> 1,
4 * copy_word_size));
}
static void build_clear_store(u32 **buf, int off)
{
if (cpu_has_64bit_gp_regs || cpu_has_64bit_zero_reg) {
uasm_i_sd(buf, GPR_ZERO, off, GPR_A0);
} else {
uasm_i_sw(buf, GPR_ZERO, off, GPR_A0);
}
}
static inline void build_clear_pref(u32 **buf, int off)
{
if (off & cache_line_mask())
return;
if (pref_bias_clear_store) {
_uasm_i_pref(buf, pref_dst_mode, pref_bias_clear_store + off,
GPR_A0);
} else if (cache_line_size == (half_clear_loop_size << 1)) {
if (cpu_has_cache_cdex_s) {
uasm_i_cache(buf, Create_Dirty_Excl_SD, off, GPR_A0);
} else if (cpu_has_cache_cdex_p) {
if (IS_ENABLED(CONFIG_WAR_R4600_V1_HIT_CACHEOP) &&
cpu_is_r4600_v1_x()) {
uasm_i_nop(buf);
uasm_i_nop(buf);
uasm_i_nop(buf);
uasm_i_nop(buf);
}
if (IS_ENABLED(CONFIG_WAR_R4600_V2_HIT_CACHEOP) &&
cpu_is_r4600_v2_x())
uasm_i_lw(buf, GPR_ZERO, GPR_ZERO, GPR_AT);
uasm_i_cache(buf, Create_Dirty_Excl_D, off, GPR_A0);
}
}
}
extern u32 __clear_page_start;
extern u32 __clear_page_end;
extern u32 __copy_page_start;
extern u32 __copy_page_end;
void build_clear_page(void)
{
int off;
u32 *buf = &__clear_page_start;
struct uasm_label *l = labels;
struct uasm_reloc *r = relocs;
int i;
static atomic_t run_once = ATOMIC_INIT(0);
if (atomic_xchg(&run_once, 1)) {
return;
}
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
set_prefetch_parameters();
/*
* This algorithm makes the following assumptions:
* - The prefetch bias is a multiple of 2 words.
* - The prefetch bias is less than one page.
*/
BUG_ON(pref_bias_clear_store % (2 * clear_word_size));
BUG_ON(PAGE_SIZE < pref_bias_clear_store);
off = PAGE_SIZE - pref_bias_clear_store;
if (off > 0xffff || !pref_bias_clear_store)
pg_addiu(&buf, GPR_A2, GPR_A0, off);
else
uasm_i_ori(&buf, GPR_A2, GPR_A0, off);
if (IS_ENABLED(CONFIG_WAR_R4600_V2_HIT_CACHEOP) && cpu_is_r4600_v2_x())
uasm_i_lui(&buf, GPR_AT, uasm_rel_hi(0xa0000000));
off = cache_line_size ? min(8, pref_bias_clear_store / cache_line_size)
* cache_line_size : 0;
while (off) {
build_clear_pref(&buf, -off);
off -= cache_line_size;
}
uasm_l_clear_pref(&l, buf);
do {
build_clear_pref(&buf, off);
build_clear_store(&buf, off);
off += clear_word_size;
} while (off < half_clear_loop_size);
pg_addiu(&buf, GPR_A0, GPR_A0, 2 * off);
off = -off;
do {
build_clear_pref(&buf, off);
if (off == -clear_word_size)
uasm_il_bne(&buf, &r, GPR_A0, GPR_A2, label_clear_pref);
build_clear_store(&buf, off);
off += clear_word_size;
} while (off < 0);
if (pref_bias_clear_store) {
pg_addiu(&buf, GPR_A2, GPR_A0, pref_bias_clear_store);
uasm_l_clear_nopref(&l, buf);
off = 0;
do {
build_clear_store(&buf, off);
off += clear_word_size;
} while (off < half_clear_loop_size);
pg_addiu(&buf, GPR_A0, GPR_A0, 2 * off);
off = -off;
do {
if (off == -clear_word_size)
uasm_il_bne(&buf, &r, GPR_A0, GPR_A2,
label_clear_nopref);
build_clear_store(&buf, off);
off += clear_word_size;
} while (off < 0);
}
uasm_i_jr(&buf, GPR_RA);
uasm_i_nop(&buf);
BUG_ON(buf > &__clear_page_end);
uasm_resolve_relocs(relocs, labels);
pr_debug("Synthesized clear page handler (%u instructions).\n",
(u32)(buf - &__clear_page_start));
pr_debug("\t.set push\n");
pr_debug("\t.set noreorder\n");
for (i = 0; i < (buf - &__clear_page_start); i++)
pr_debug("\t.word 0x%08x\n", (&__clear_page_start)[i]);
pr_debug("\t.set pop\n");
}
static void build_copy_load(u32 **buf, int reg, int off)
{
if (cpu_has_64bit_gp_regs) {
uasm_i_ld(buf, reg, off, GPR_A1);
} else {
uasm_i_lw(buf, reg, off, GPR_A1);
}
}
static void build_copy_store(u32 **buf, int reg, int off)
{
if (cpu_has_64bit_gp_regs) {
uasm_i_sd(buf, reg, off, GPR_A0);
} else {
uasm_i_sw(buf, reg, off, GPR_A0);
}
}
static inline void build_copy_load_pref(u32 **buf, int off)
{
if (off & cache_line_mask())
return;
if (pref_bias_copy_load)
_uasm_i_pref(buf, pref_src_mode, pref_bias_copy_load + off, GPR_A1);
}
static inline void build_copy_store_pref(u32 **buf, int off)
{
if (off & cache_line_mask())
return;
if (pref_bias_copy_store) {
_uasm_i_pref(buf, pref_dst_mode, pref_bias_copy_store + off,
GPR_A0);
} else if (cache_line_size == (half_copy_loop_size << 1)) {
if (cpu_has_cache_cdex_s) {
uasm_i_cache(buf, Create_Dirty_Excl_SD, off, GPR_A0);
} else if (cpu_has_cache_cdex_p) {
if (IS_ENABLED(CONFIG_WAR_R4600_V1_HIT_CACHEOP) &&
cpu_is_r4600_v1_x()) {
uasm_i_nop(buf);
uasm_i_nop(buf);
uasm_i_nop(buf);
uasm_i_nop(buf);
}
if (IS_ENABLED(CONFIG_WAR_R4600_V2_HIT_CACHEOP) &&
cpu_is_r4600_v2_x())
uasm_i_lw(buf, GPR_ZERO, GPR_ZERO, GPR_AT);
uasm_i_cache(buf, Create_Dirty_Excl_D, off, GPR_A0);
}
}
}
void build_copy_page(void)
{
int off;
u32 *buf = &__copy_page_start;
struct uasm_label *l = labels;
struct uasm_reloc *r = relocs;
int i;
static atomic_t run_once = ATOMIC_INIT(0);
if (atomic_xchg(&run_once, 1)) {
return;
}
memset(labels, 0, sizeof(labels));
memset(relocs, 0, sizeof(relocs));
set_prefetch_parameters();
/*
* This algorithm makes the following assumptions:
* - All prefetch biases are multiples of 8 words.
* - The prefetch biases are less than one page.
* - The store prefetch bias isn't greater than the load
* prefetch bias.
*/
BUG_ON(pref_bias_copy_load % (8 * copy_word_size));
BUG_ON(pref_bias_copy_store % (8 * copy_word_size));
BUG_ON(PAGE_SIZE < pref_bias_copy_load);
BUG_ON(pref_bias_copy_store > pref_bias_copy_load);
off = PAGE_SIZE - pref_bias_copy_load;
if (off > 0xffff || !pref_bias_copy_load)
pg_addiu(&buf, GPR_A2, GPR_A0, off);
else
uasm_i_ori(&buf, GPR_A2, GPR_A0, off);
if (IS_ENABLED(CONFIG_WAR_R4600_V2_HIT_CACHEOP) && cpu_is_r4600_v2_x())
uasm_i_lui(&buf, GPR_AT, uasm_rel_hi(0xa0000000));
off = cache_line_size ? min(8, pref_bias_copy_load / cache_line_size) *
cache_line_size : 0;
while (off) {
build_copy_load_pref(&buf, -off);
off -= cache_line_size;
}
off = cache_line_size ? min(8, pref_bias_copy_store / cache_line_size) *
cache_line_size : 0;
while (off) {
build_copy_store_pref(&buf, -off);
off -= cache_line_size;
}
uasm_l_copy_pref_both(&l, buf);
do {
build_copy_load_pref(&buf, off);
build_copy_load(&buf, GPR_T0, off);
build_copy_load_pref(&buf, off + copy_word_size);
build_copy_load(&buf, GPR_T1, off + copy_word_size);
build_copy_load_pref(&buf, off + 2 * copy_word_size);
build_copy_load(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_load_pref(&buf, off + 3 * copy_word_size);
build_copy_load(&buf, GPR_T3, off + 3 * copy_word_size);
build_copy_store_pref(&buf, off);
build_copy_store(&buf, GPR_T0, off);
build_copy_store_pref(&buf, off + copy_word_size);
build_copy_store(&buf, GPR_T1, off + copy_word_size);
build_copy_store_pref(&buf, off + 2 * copy_word_size);
build_copy_store(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_store_pref(&buf, off + 3 * copy_word_size);
build_copy_store(&buf, GPR_T3, off + 3 * copy_word_size);
off += 4 * copy_word_size;
} while (off < half_copy_loop_size);
pg_addiu(&buf, GPR_A1, GPR_A1, 2 * off);
pg_addiu(&buf, GPR_A0, GPR_A0, 2 * off);
off = -off;
do {
build_copy_load_pref(&buf, off);
build_copy_load(&buf, GPR_T0, off);
build_copy_load_pref(&buf, off + copy_word_size);
build_copy_load(&buf, GPR_T1, off + copy_word_size);
build_copy_load_pref(&buf, off + 2 * copy_word_size);
build_copy_load(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_load_pref(&buf, off + 3 * copy_word_size);
build_copy_load(&buf, GPR_T3, off + 3 * copy_word_size);
build_copy_store_pref(&buf, off);
build_copy_store(&buf, GPR_T0, off);
build_copy_store_pref(&buf, off + copy_word_size);
build_copy_store(&buf, GPR_T1, off + copy_word_size);
build_copy_store_pref(&buf, off + 2 * copy_word_size);
build_copy_store(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_store_pref(&buf, off + 3 * copy_word_size);
if (off == -(4 * copy_word_size))
uasm_il_bne(&buf, &r, GPR_A2, GPR_A0, label_copy_pref_both);
build_copy_store(&buf, GPR_T3, off + 3 * copy_word_size);
off += 4 * copy_word_size;
} while (off < 0);
if (pref_bias_copy_load - pref_bias_copy_store) {
pg_addiu(&buf, GPR_A2, GPR_A0,
pref_bias_copy_load - pref_bias_copy_store);
uasm_l_copy_pref_store(&l, buf);
off = 0;
do {
build_copy_load(&buf, GPR_T0, off);
build_copy_load(&buf, GPR_T1, off + copy_word_size);
build_copy_load(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_load(&buf, GPR_T3, off + 3 * copy_word_size);
build_copy_store_pref(&buf, off);
build_copy_store(&buf, GPR_T0, off);
build_copy_store_pref(&buf, off + copy_word_size);
build_copy_store(&buf, GPR_T1, off + copy_word_size);
build_copy_store_pref(&buf, off + 2 * copy_word_size);
build_copy_store(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_store_pref(&buf, off + 3 * copy_word_size);
build_copy_store(&buf, GPR_T3, off + 3 * copy_word_size);
off += 4 * copy_word_size;
} while (off < half_copy_loop_size);
pg_addiu(&buf, GPR_A1, GPR_A1, 2 * off);
pg_addiu(&buf, GPR_A0, GPR_A0, 2 * off);
off = -off;
do {
build_copy_load(&buf, GPR_T0, off);
build_copy_load(&buf, GPR_T1, off + copy_word_size);
build_copy_load(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_load(&buf, GPR_T3, off + 3 * copy_word_size);
build_copy_store_pref(&buf, off);
build_copy_store(&buf, GPR_T0, off);
build_copy_store_pref(&buf, off + copy_word_size);
build_copy_store(&buf, GPR_T1, off + copy_word_size);
build_copy_store_pref(&buf, off + 2 * copy_word_size);
build_copy_store(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_store_pref(&buf, off + 3 * copy_word_size);
if (off == -(4 * copy_word_size))
uasm_il_bne(&buf, &r, GPR_A2, GPR_A0,
label_copy_pref_store);
build_copy_store(&buf, GPR_T3, off + 3 * copy_word_size);
off += 4 * copy_word_size;
} while (off < 0);
}
if (pref_bias_copy_store) {
pg_addiu(&buf, GPR_A2, GPR_A0, pref_bias_copy_store);
uasm_l_copy_nopref(&l, buf);
off = 0;
do {
build_copy_load(&buf, GPR_T0, off);
build_copy_load(&buf, GPR_T1, off + copy_word_size);
build_copy_load(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_load(&buf, GPR_T3, off + 3 * copy_word_size);
build_copy_store(&buf, GPR_T0, off);
build_copy_store(&buf, GPR_T1, off + copy_word_size);
build_copy_store(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_store(&buf, GPR_T3, off + 3 * copy_word_size);
off += 4 * copy_word_size;
} while (off < half_copy_loop_size);
pg_addiu(&buf, GPR_A1, GPR_A1, 2 * off);
pg_addiu(&buf, GPR_A0, GPR_A0, 2 * off);
off = -off;
do {
build_copy_load(&buf, GPR_T0, off);
build_copy_load(&buf, GPR_T1, off + copy_word_size);
build_copy_load(&buf, GPR_T2, off + 2 * copy_word_size);
build_copy_load(&buf, GPR_T3, off + 3 * copy_word_size);
build_copy_store(&buf, GPR_T0, off);
build_copy_store(&buf, GPR_T1, off + copy_word_size);
build_copy_store(&buf, GPR_T2, off + 2 * copy_word_size);
if (off == -(4 * copy_word_size))
uasm_il_bne(&buf, &r, GPR_A2, GPR_A0,
label_copy_nopref);
build_copy_store(&buf, GPR_T3, off + 3 * copy_word_size);
off += 4 * copy_word_size;
} while (off < 0);
}
uasm_i_jr(&buf, GPR_RA);
uasm_i_nop(&buf);
BUG_ON(buf > &__copy_page_end);
uasm_resolve_relocs(relocs, labels);
pr_debug("Synthesized copy page handler (%u instructions).\n",
(u32)(buf - &__copy_page_start));
pr_debug("\t.set push\n");
pr_debug("\t.set noreorder\n");
for (i = 0; i < (buf - &__copy_page_start); i++)
pr_debug("\t.word 0x%08x\n", (&__copy_page_start)[i]);
pr_debug("\t.set pop\n");
}
#ifdef CONFIG_SIBYTE_DMA_PAGEOPS
extern void clear_page_cpu(void *page);
extern void copy_page_cpu(void *to, void *from);
/*
* Pad descriptors to cacheline, since each is exclusively owned by a
* particular CPU.
*/
struct dmadscr {
u64 dscr_a;
u64 dscr_b;
u64 pad_a;
u64 pad_b;
} ____cacheline_aligned_in_smp page_descr[DM_NUM_CHANNELS];
void clear_page(void *page)
{
u64 to_phys = CPHYSADDR((unsigned long)page);
unsigned int cpu = smp_processor_id();
/* if the page is not in KSEG0, use old way */
if ((long)KSEGX((unsigned long)page) != (long)CKSEG0)
return clear_page_cpu(page);
page_descr[cpu].dscr_a = to_phys | M_DM_DSCRA_ZERO_MEM |
M_DM_DSCRA_L2C_DEST | M_DM_DSCRA_INTERRUPT;
page_descr[cpu].dscr_b = V_DM_DSCRB_SRC_LENGTH(PAGE_SIZE);
__raw_writeq(1, IOADDR(A_DM_REGISTER(cpu, R_DM_DSCR_COUNT)));
/*
* Don't really want to do it this way, but there's no
* reliable way to delay completion detection.
*/
while (!(__raw_readq(IOADDR(A_DM_REGISTER(cpu, R_DM_DSCR_BASE_DEBUG)))
& M_DM_DSCR_BASE_INTERRUPT))
;
__raw_readq(IOADDR(A_DM_REGISTER(cpu, R_DM_DSCR_BASE)));
}
EXPORT_SYMBOL(clear_page);
void copy_page(void *to, void *from)
{
u64 from_phys = CPHYSADDR((unsigned long)from);
u64 to_phys = CPHYSADDR((unsigned long)to);
unsigned int cpu = smp_processor_id();
/* if any page is not in KSEG0, use old way */
if ((long)KSEGX((unsigned long)to) != (long)CKSEG0
|| (long)KSEGX((unsigned long)from) != (long)CKSEG0)
return copy_page_cpu(to, from);
page_descr[cpu].dscr_a = to_phys | M_DM_DSCRA_L2C_DEST |
M_DM_DSCRA_INTERRUPT;
page_descr[cpu].dscr_b = from_phys | V_DM_DSCRB_SRC_LENGTH(PAGE_SIZE);
__raw_writeq(1, IOADDR(A_DM_REGISTER(cpu, R_DM_DSCR_COUNT)));
/*
* Don't really want to do it this way, but there's no
* reliable way to delay completion detection.
*/
while (!(__raw_readq(IOADDR(A_DM_REGISTER(cpu, R_DM_DSCR_BASE_DEBUG)))
& M_DM_DSCR_BASE_INTERRUPT))
;
__raw_readq(IOADDR(A_DM_REGISTER(cpu, R_DM_DSCR_BASE)));
}
EXPORT_SYMBOL(copy_page);
#endif /* CONFIG_SIBYTE_DMA_PAGEOPS */
+79
View File
@@ -0,0 +1,79 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 2003 by Ralf Baechle
*/
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/memblock.h>
#include <linux/highmem.h>
#include <asm/fixmap.h>
#include <asm/pgalloc.h>
#include <asm/tlbflush.h>
void pgd_init(void *addr)
{
unsigned long *p = (unsigned long *)addr;
int i;
for (i = 0; i < USER_PTRS_PER_PGD; i+=8) {
p[i + 0] = (unsigned long) invalid_pte_table;
p[i + 1] = (unsigned long) invalid_pte_table;
p[i + 2] = (unsigned long) invalid_pte_table;
p[i + 3] = (unsigned long) invalid_pte_table;
p[i + 4] = (unsigned long) invalid_pte_table;
p[i + 5] = (unsigned long) invalid_pte_table;
p[i + 6] = (unsigned long) invalid_pte_table;
p[i + 7] = (unsigned long) invalid_pte_table;
}
}
#if defined(CONFIG_TRANSPARENT_HUGEPAGE)
void set_pmd_at(struct mm_struct *mm, unsigned long addr,
pmd_t *pmdp, pmd_t pmd)
{
*pmdp = pmd;
}
#endif /* defined(CONFIG_TRANSPARENT_HUGEPAGE) */
void __init pagetable_init(void)
{
unsigned long vaddr;
pgd_t *pgd_base;
#ifdef CONFIG_HIGHMEM
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
#endif
/* Initialize the entire pgd. */
pgd_init(swapper_pg_dir);
pgd_init(&swapper_pg_dir[USER_PTRS_PER_PGD]);
pgd_base = swapper_pg_dir;
/*
* Fixed mappings:
*/
vaddr = __fix_to_virt(__end_of_fixed_addresses - 1);
fixrange_init(vaddr & PMD_MASK, vaddr + FIXADDR_SIZE, pgd_base);
#ifdef CONFIG_HIGHMEM
/*
* Permanent kmaps:
*/
vaddr = PKMAP_BASE;
fixrange_init(vaddr & PMD_MASK, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);
pgd = swapper_pg_dir + pgd_index(vaddr);
p4d = p4d_offset(pgd, vaddr);
pud = pud_offset(p4d, vaddr);
pmd = pmd_offset(pud, vaddr);
pte = pte_offset_kernel(pmd, vaddr);
pkmap_page_table = pte;
#endif
}
+119
View File
@@ -0,0 +1,119 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1999, 2000 by Silicon Graphics
* Copyright (C) 2003 by Ralf Baechle
*/
#include <linux/export.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <asm/fixmap.h>
#include <asm/pgalloc.h>
#include <asm/tlbflush.h>
void pgd_init(void *addr)
{
unsigned long *p, *end;
unsigned long entry;
#if !defined(__PAGETABLE_PUD_FOLDED)
entry = (unsigned long)invalid_pud_table;
#elif !defined(__PAGETABLE_PMD_FOLDED)
entry = (unsigned long)invalid_pmd_table;
#else
entry = (unsigned long)invalid_pte_table;
#endif
p = (unsigned long *) addr;
end = p + PTRS_PER_PGD;
do {
p[0] = entry;
p[1] = entry;
p[2] = entry;
p[3] = entry;
p[4] = entry;
p += 8;
p[-3] = entry;
p[-2] = entry;
p[-1] = entry;
} while (p != end);
}
#ifndef __PAGETABLE_PMD_FOLDED
void pmd_init(void *addr)
{
unsigned long *p, *end;
unsigned long pagetable = (unsigned long)invalid_pte_table;
p = (unsigned long *)addr;
end = p + PTRS_PER_PMD;
do {
p[0] = pagetable;
p[1] = pagetable;
p[2] = pagetable;
p[3] = pagetable;
p[4] = pagetable;
p += 8;
p[-3] = pagetable;
p[-2] = pagetable;
p[-1] = pagetable;
} while (p != end);
}
EXPORT_SYMBOL_GPL(pmd_init);
#endif
#ifndef __PAGETABLE_PUD_FOLDED
void pud_init(void *addr)
{
unsigned long *p, *end;
unsigned long pagetable = (unsigned long)invalid_pmd_table;
p = (unsigned long *)addr;
end = p + PTRS_PER_PUD;
do {
p[0] = pagetable;
p[1] = pagetable;
p[2] = pagetable;
p[3] = pagetable;
p[4] = pagetable;
p += 8;
p[-3] = pagetable;
p[-2] = pagetable;
p[-1] = pagetable;
} while (p != end);
}
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
void set_pmd_at(struct mm_struct *mm, unsigned long addr,
pmd_t *pmdp, pmd_t pmd)
{
*pmdp = pmd;
}
#endif
void __init pagetable_init(void)
{
unsigned long vaddr;
pgd_t *pgd_base;
/* Initialize the entire pgd. */
pgd_init(swapper_pg_dir);
#ifndef __PAGETABLE_PUD_FOLDED
pud_init(invalid_pud_table);
#endif
#ifndef __PAGETABLE_PMD_FOLDED
pmd_init(invalid_pmd_table);
#endif
pgd_base = swapper_pg_dir;
/*
* Fixed mappings:
*/
vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
fixrange_init(vaddr, vaddr + FIXADDR_SIZE, pgd_base);
}
+25
View File
@@ -0,0 +1,25 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*/
#include <linux/export.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <asm/pgalloc.h>
pgd_t *pgd_alloc(struct mm_struct *mm)
{
pgd_t *init, *ret;
ret = __pgd_alloc(mm, PGD_TABLE_ORDER);
if (ret) {
init = pgd_offset(&init_mm, 0UL);
pgd_init(ret);
memcpy(ret + USER_PTRS_PER_PGD, init + USER_PTRS_PER_PGD,
(PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));
}
return ret;
}
EXPORT_SYMBOL_GPL(pgd_alloc);
+50
View File
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bug.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/mmdebug.h>
#include <linux/mm.h>
#include <asm/addrspace.h>
#include <asm/sections.h>
#include <asm/io.h>
#include <asm/page.h>
#include <asm/dma.h>
static inline bool __debug_virt_addr_valid(unsigned long x)
{
/*
* MAX_DMA_ADDRESS is a virtual address that may not correspond to an
* actual physical address. Enough code relies on
* virt_to_phys(MAX_DMA_ADDRESS) that we just need to work around it
* and always return true.
*/
if (x == MAX_DMA_ADDRESS)
return true;
return x >= PAGE_OFFSET && (KSEGX(x) < KSEG2 ||
IS_ENABLED(CONFIG_EVA) ||
!IS_ENABLED(CONFIG_HIGHMEM));
}
phys_addr_t __virt_to_phys(volatile const void *x)
{
WARN(!__debug_virt_addr_valid((unsigned long)x),
"virt_to_phys used for non-linear address: %p (%pS)\n",
x, x);
return __virt_to_phys_nodebug(x);
}
EXPORT_SYMBOL(__virt_to_phys);
phys_addr_t __phys_addr_symbol(unsigned long x)
{
/* This is bounds checking against the kernel image only.
* __pa_symbol should only be used on kernel symbol addresses.
*/
VIRTUAL_BUG_ON(x < (unsigned long)_text ||
x > (unsigned long)_end);
return __pa_symbol_nodebug(x);
}
EXPORT_SYMBOL(__phys_addr_symbol);
+61
View File
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Imagination Technologies
* Author: Paul Burton <paul.burton@mips.com>
*/
#include <asm/bcache.h>
#include <asm/debug.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/init.h>
static ssize_t sc_prefetch_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
bool enabled = bc_prefetch_is_enabled();
char buf[3];
buf[0] = enabled ? 'Y' : 'N';
buf[1] = '\n';
buf[2] = 0;
return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
}
static ssize_t sc_prefetch_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
bool enabled;
int err;
err = kstrtobool_from_user(user_buf, count, &enabled);
if (err)
return err;
if (enabled)
bc_prefetch_enable();
else
bc_prefetch_disable();
return count;
}
static const struct file_operations sc_prefetch_fops = {
.open = simple_open,
.llseek = default_llseek,
.read = sc_prefetch_read,
.write = sc_prefetch_write,
};
static int __init sc_debugfs_init(void)
{
struct dentry *dir;
dir = debugfs_create_dir("l2cache", mips_debugfs_dir);
debugfs_create_file("prefetch", S_IRUGO | S_IWUSR, dir, NULL,
&sc_prefetch_fops);
return 0;
}
late_initcall(sc_debugfs_init);
+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sc-ip22.c: Indy cache management functions.
*
* Copyright (C) 1997, 2001 Ralf Baechle (ralf@gnu.org),
* derived from r4xx0.c by David S. Miller (davem@davemloft.net).
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/bcache.h>
#include <asm/page.h>
#include <asm/bootinfo.h>
#include <asm/sgi/ip22.h>
#include <asm/sgi/mc.h>
/* Secondary cache size in bytes, if present. */
static unsigned long scache_size;
#undef DEBUG_CACHE
#define SC_SIZE 0x00080000
#define SC_LINE 32
#define CI_MASK (SC_SIZE - SC_LINE)
#define SC_INDEX(n) ((n) & CI_MASK)
static inline void indy_sc_wipe(unsigned long first, unsigned long last)
{
unsigned long tmp;
__asm__ __volatile__(
" .set push # indy_sc_wipe \n"
" .set noreorder \n"
" .set mips3 \n"
" .set noat \n"
" mfc0 %2, $12 \n"
" li $1, 0x80 # Go 64 bit \n"
" mtc0 $1, $12 \n"
" \n"
" # \n"
" # Open code a dli $1, 0x9000000080000000 \n"
" # \n"
" # Required because binutils 2.25 will happily accept \n"
" # 64 bit instructions in .set mips3 mode but puke on \n"
" # 64 bit constants when generating 32 bit ELF \n"
" # \n"
" lui $1,0x9000 \n"
" dsll $1,$1,0x10 \n"
" ori $1,$1,0x8000 \n"
" dsll $1,$1,0x10 \n"
" \n"
" or %0, $1 # first line to flush \n"
" or %1, $1 # last line to flush \n"
" .set at \n"
" \n"
"1: sw $0, 0(%0) \n"
" bne %0, %1, 1b \n"
" daddu %0, 32 \n"
" \n"
" mtc0 %2, $12 # Back to 32 bit \n"
" nop # pipeline hazard \n"
" nop \n"
" nop \n"
" nop \n"
" .set pop \n"
: "=r" (first), "=r" (last), "=&r" (tmp)
: "0" (first), "1" (last));
}
static void indy_sc_wback_invalidate(unsigned long addr, unsigned long size)
{
unsigned long first_line, last_line;
unsigned long flags;
#ifdef DEBUG_CACHE
printk("indy_sc_wback_invalidate[%08lx,%08lx]", addr, size);
#endif
/* Catch bad driver code */
BUG_ON(size == 0);
/* Which lines to flush? */
first_line = SC_INDEX(addr);
last_line = SC_INDEX(addr + size - 1);
local_irq_save(flags);
if (first_line <= last_line) {
indy_sc_wipe(first_line, last_line);
goto out;
}
indy_sc_wipe(first_line, SC_SIZE - SC_LINE);
indy_sc_wipe(0, last_line);
out:
local_irq_restore(flags);
}
static void indy_sc_enable(void)
{
unsigned long addr, tmp1, tmp2;
/* This is really cool... */
#ifdef DEBUG_CACHE
printk("Enabling R4600 SCACHE\n");
#endif
__asm__ __volatile__(
".set\tpush\n\t"
".set\tnoreorder\n\t"
".set\tmips3\n\t"
"mfc0\t%2, $12\n\t"
"nop; nop; nop; nop;\n\t"
"li\t%1, 0x80\n\t"
"mtc0\t%1, $12\n\t"
"nop; nop; nop; nop;\n\t"
"li\t%0, 0x1\n\t"
"dsll\t%0, 31\n\t"
"lui\t%1, 0x9000\n\t"
"dsll32\t%1, 0\n\t"
"or\t%0, %1, %0\n\t"
"sb\t$0, 0(%0)\n\t"
"mtc0\t$0, $12\n\t"
"nop; nop; nop; nop;\n\t"
"mtc0\t%2, $12\n\t"
"nop; nop; nop; nop;\n\t"
".set\tpop"
: "=r" (tmp1), "=r" (tmp2), "=r" (addr));
}
static void indy_sc_disable(void)
{
unsigned long tmp1, tmp2, tmp3;
#ifdef DEBUG_CACHE
printk("Disabling R4600 SCACHE\n");
#endif
__asm__ __volatile__(
".set\tpush\n\t"
".set\tnoreorder\n\t"
".set\tmips3\n\t"
"li\t%0, 0x1\n\t"
"dsll\t%0, 31\n\t"
"lui\t%1, 0x9000\n\t"
"dsll32\t%1, 0\n\t"
"or\t%0, %1, %0\n\t"
"mfc0\t%2, $12\n\t"
"nop; nop; nop; nop\n\t"
"li\t%1, 0x80\n\t"
"mtc0\t%1, $12\n\t"
"nop; nop; nop; nop\n\t"
"sh\t$0, 0(%0)\n\t"
"mtc0\t$0, $12\n\t"
"nop; nop; nop; nop\n\t"
"mtc0\t%2, $12\n\t"
"nop; nop; nop; nop\n\t"
".set\tpop"
: "=r" (tmp1), "=r" (tmp2), "=r" (tmp3));
}
static inline int __init indy_sc_probe(void)
{
unsigned int size = ip22_eeprom_read(&sgimc->eeprom, 17);
if (size == 0)
return 0;
size <<= PAGE_SHIFT;
printk(KERN_INFO "R4600/R5000 SCACHE size %dK, linesize 32 bytes.\n",
size >> 10);
scache_size = size;
return 1;
}
/* XXX Check with wje if the Indy caches can differentiate between
writeback + invalidate and just invalidate. */
static struct bcache_ops indy_sc_ops = {
.bc_enable = indy_sc_enable,
.bc_disable = indy_sc_disable,
.bc_wback_inv = indy_sc_wback_invalidate,
.bc_inv = indy_sc_wback_invalidate
};
void indy_sc_init(void)
{
if (indy_sc_probe()) {
indy_sc_enable();
bcops = &indy_sc_ops;
}
}
+264
View File
@@ -0,0 +1,264 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2006 Chris Dearman (chris@mips.com),
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/cpu-type.h>
#include <asm/mipsregs.h>
#include <asm/bcache.h>
#include <asm/cacheops.h>
#include <asm/page.h>
#include <asm/mmu_context.h>
#include <asm/r4kcache.h>
#include <asm/mips-cps.h>
#include <asm/bootinfo.h>
/*
* MIPS32/MIPS64 L2 cache handling
*/
/*
* Writeback and invalidate the secondary cache before DMA.
*/
static void mips_sc_wback_inv(unsigned long addr, unsigned long size)
{
blast_scache_range(addr, addr + size);
}
/*
* Invalidate the secondary cache before DMA.
*/
static void mips_sc_inv(unsigned long addr, unsigned long size)
{
unsigned long lsize = cpu_scache_line_size();
unsigned long almask = ~(lsize - 1);
cache_op(Hit_Writeback_Inv_SD, addr & almask);
cache_op(Hit_Writeback_Inv_SD, (addr + size - 1) & almask);
blast_inv_scache_range(addr, addr + size);
}
static void mips_sc_enable(void)
{
/* L2 cache is permanently enabled */
}
static void mips_sc_disable(void)
{
/* L2 cache is permanently enabled */
}
static void mips_sc_prefetch_enable(void)
{
unsigned long pftctl;
if (mips_cm_revision() < CM_REV_CM2_5)
return;
/*
* If there is one or more L2 prefetch unit present then enable
* prefetching for both code & data, for all ports.
*/
pftctl = read_gcr_l2_pft_control();
if (pftctl & CM_GCR_L2_PFT_CONTROL_NPFT) {
pftctl &= ~CM_GCR_L2_PFT_CONTROL_PAGEMASK;
pftctl |= PAGE_MASK & CM_GCR_L2_PFT_CONTROL_PAGEMASK;
pftctl |= CM_GCR_L2_PFT_CONTROL_PFTEN;
write_gcr_l2_pft_control(pftctl);
set_gcr_l2_pft_control_b(CM_GCR_L2_PFT_CONTROL_B_PORTID |
CM_GCR_L2_PFT_CONTROL_B_CEN);
}
}
static void mips_sc_prefetch_disable(void)
{
if (mips_cm_revision() < CM_REV_CM2_5)
return;
clear_gcr_l2_pft_control(CM_GCR_L2_PFT_CONTROL_PFTEN);
clear_gcr_l2_pft_control_b(CM_GCR_L2_PFT_CONTROL_B_PORTID |
CM_GCR_L2_PFT_CONTROL_B_CEN);
}
static bool mips_sc_prefetch_is_enabled(void)
{
unsigned long pftctl;
if (mips_cm_revision() < CM_REV_CM2_5)
return false;
pftctl = read_gcr_l2_pft_control();
if (!(pftctl & CM_GCR_L2_PFT_CONTROL_NPFT))
return false;
return !!(pftctl & CM_GCR_L2_PFT_CONTROL_PFTEN);
}
static struct bcache_ops mips_sc_ops = {
.bc_enable = mips_sc_enable,
.bc_disable = mips_sc_disable,
.bc_wback_inv = mips_sc_wback_inv,
.bc_inv = mips_sc_inv,
.bc_prefetch_enable = mips_sc_prefetch_enable,
.bc_prefetch_disable = mips_sc_prefetch_disable,
.bc_prefetch_is_enabled = mips_sc_prefetch_is_enabled,
};
/*
* Check if the L2 cache controller is activated on a particular platform.
* MTI's L2 controller and the L2 cache controller of Broadcom's BMIPS
* cores both use c0_config2's bit 12 as "L2 Bypass" bit, that is the
* cache being disabled. However there is no guarantee for this to be
* true on all platforms. In an act of stupidity the spec defined bits
* 12..15 as implementation defined so below function will eventually have
* to be replaced by a platform specific probe.
*/
static inline int mips_sc_is_activated(struct cpuinfo_mips *c)
{
unsigned int config2 = read_c0_config2();
unsigned int tmp;
/* Check the bypass bit (L2B) */
switch (current_cpu_type()) {
case CPU_34K:
case CPU_74K:
case CPU_1004K:
case CPU_1074K:
case CPU_INTERAPTIV:
case CPU_PROAPTIV:
case CPU_P5600:
case CPU_BMIPS5000:
case CPU_QEMU_GENERIC:
case CPU_P6600:
if (config2 & (1 << 12))
return 0;
}
tmp = (config2 >> 4) & 0x0f;
if (0 < tmp && tmp <= 7)
c->scache.linesz = 2 << tmp;
else
return 0;
return 1;
}
static int mips_sc_probe_cm3(void)
{
struct cpuinfo_mips *c = &current_cpu_data;
unsigned long cfg = read_gcr_l2_config();
unsigned long sets, line_sz, assoc;
if (cfg & CM_GCR_L2_CONFIG_BYPASS)
return 0;
sets = cfg & CM_GCR_L2_CONFIG_SET_SIZE;
sets >>= __ffs(CM_GCR_L2_CONFIG_SET_SIZE);
if (sets)
c->scache.sets = 64 << sets;
line_sz = cfg & CM_GCR_L2_CONFIG_LINE_SIZE;
line_sz >>= __ffs(CM_GCR_L2_CONFIG_LINE_SIZE);
if (line_sz)
c->scache.linesz = 2 << line_sz;
assoc = cfg & CM_GCR_L2_CONFIG_ASSOC;
assoc >>= __ffs(CM_GCR_L2_CONFIG_ASSOC);
c->scache.ways = assoc + 1;
c->scache.waysize = c->scache.sets * c->scache.linesz;
c->scache.waybit = __ffs(c->scache.waysize);
if (c->scache.linesz) {
c->scache.flags &= ~MIPS_CACHE_NOT_PRESENT;
c->options |= MIPS_CPU_INCLUSIVE_CACHES;
return 1;
}
return 0;
}
static inline int mips_sc_probe(void)
{
struct cpuinfo_mips *c = &current_cpu_data;
unsigned int config1, config2;
unsigned int tmp;
/* Mark as not present until probe completed */
c->scache.flags |= MIPS_CACHE_NOT_PRESENT;
if (mips_cm_revision() >= CM_REV_CM3)
return mips_sc_probe_cm3();
/* Ignore anything but MIPSxx processors */
if (!(c->isa_level & (MIPS_CPU_ISA_M32R1 | MIPS_CPU_ISA_M64R1 |
MIPS_CPU_ISA_M32R2 | MIPS_CPU_ISA_M64R2 |
MIPS_CPU_ISA_M32R5 | MIPS_CPU_ISA_M64R5 |
MIPS_CPU_ISA_M32R6 | MIPS_CPU_ISA_M64R6)))
return 0;
/* Does this MIPS32/MIPS64 CPU have a config2 register? */
config1 = read_c0_config1();
if (!(config1 & MIPS_CONF_M))
return 0;
config2 = read_c0_config2();
if (!mips_sc_is_activated(c))
return 0;
tmp = (config2 >> 8) & 0x0f;
if (tmp <= 7)
c->scache.sets = 64 << tmp;
else
return 0;
tmp = (config2 >> 0) & 0x0f;
if (tmp <= 7)
c->scache.ways = tmp + 1;
else
return 0;
if (current_cpu_type() == CPU_XBURST) {
switch (mips_machtype) {
/*
* According to config2 it would be 5-ways, but that is
* contradicted by all documentation.
*/
case MACH_INGENIC_JZ4770:
case MACH_INGENIC_JZ4775:
c->scache.ways = 4;
break;
/*
* According to config2 it would be 5-ways and 512-sets,
* but that is contradicted by all documentation.
*/
case MACH_INGENIC_X1000:
case MACH_INGENIC_X1000E:
c->scache.sets = 256;
c->scache.ways = 4;
break;
}
}
c->scache.waysize = c->scache.sets * c->scache.linesz;
c->scache.waybit = __ffs(c->scache.waysize);
c->scache.flags &= ~MIPS_CACHE_NOT_PRESENT;
return 1;
}
int mips_sc_init(void)
{
int found = mips_sc_probe();
if (found) {
mips_sc_enable();
mips_sc_prefetch_enable();
bcops = &mips_sc_ops;
}
return found;
}
+107
View File
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1997, 2001 Ralf Baechle (ralf@gnu.org),
* derived from r4xx0.c by David S. Miller (davem@davemloft.net).
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/mipsregs.h>
#include <asm/bcache.h>
#include <asm/cacheops.h>
#include <asm/page.h>
#include <asm/mmu_context.h>
#include <asm/r4kcache.h>
/* Secondary cache size in bytes, if present. */
static unsigned long scache_size;
#define SC_LINE 32
#define SC_PAGE (128*SC_LINE)
static inline void blast_r5000_scache(void)
{
unsigned long start = INDEX_BASE;
unsigned long end = start + scache_size;
while(start < end) {
cache_op(R5K_Page_Invalidate_S, start);
start += SC_PAGE;
}
}
static void r5k_dma_cache_inv_sc(unsigned long addr, unsigned long size)
{
unsigned long end, a;
/* Catch bad driver code */
BUG_ON(size == 0);
if (size >= scache_size) {
blast_r5000_scache();
return;
}
/* On the R5000 secondary cache we cannot
* invalidate less than a page at a time.
* The secondary cache is physically indexed, write-through.
*/
a = addr & ~(SC_PAGE - 1);
end = (addr + size - 1) & ~(SC_PAGE - 1);
while (a <= end) {
cache_op(R5K_Page_Invalidate_S, a);
a += SC_PAGE;
}
}
static void r5k_sc_enable(void)
{
unsigned long flags;
local_irq_save(flags);
set_c0_config(R5K_CONF_SE);
blast_r5000_scache();
local_irq_restore(flags);
}
static void r5k_sc_disable(void)
{
unsigned long flags;
local_irq_save(flags);
blast_r5000_scache();
clear_c0_config(R5K_CONF_SE);
local_irq_restore(flags);
}
static inline int __init r5k_sc_probe(void)
{
unsigned long config = read_c0_config();
if (config & CONF_SC)
return 0;
scache_size = (512 * 1024) << ((config & R5K_CONF_SS) >> 20);
printk("R5000 SCACHE size %ldkB, linesize 32 bytes.\n",
scache_size >> 10);
return 1;
}
static struct bcache_ops r5k_sc_ops = {
.bc_enable = r5k_sc_enable,
.bc_disable = r5k_sc_disable,
.bc_wback_inv = r5k_dma_cache_inv_sc,
.bc_inv = r5k_dma_cache_inv_sc
};
void r5k_sc_init(void)
{
if (r5k_sc_probe()) {
r5k_sc_enable();
bcops = &r5k_sc_ops;
}
}
+270
View File
@@ -0,0 +1,270 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sc-rm7k.c: RM7000 cache management functions.
*
* Copyright (C) 1997, 2001, 2003, 2004 Ralf Baechle (ralf@linux-mips.org)
*/
#undef DEBUG
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/bitops.h>
#include <asm/addrspace.h>
#include <asm/bcache.h>
#include <asm/cacheops.h>
#include <asm/mipsregs.h>
#include <asm/processor.h>
#include <asm/sections.h>
#include <asm/cacheflush.h> /* for run_uncached() */
/* Primary cache parameters. */
#define sc_lsize 32
#define tc_pagesize (32*128)
/* Secondary cache parameters. */
#define scache_size (256*1024) /* Fixed to 256KiB on RM7000 */
/* Tertiary cache parameters */
#define tc_lsize 32
extern unsigned long icache_way_size, dcache_way_size;
static unsigned long tcache_size;
#include <asm/r4kcache.h>
static int rm7k_tcache_init;
/*
* Writeback and invalidate the primary cache dcache before DMA.
* (XXX These need to be fixed ...)
*/
static void rm7k_sc_wback_inv(unsigned long addr, unsigned long size)
{
unsigned long end, a;
pr_debug("rm7k_sc_wback_inv[%08lx,%08lx]", addr, size);
/* Catch bad driver code */
BUG_ON(size == 0);
blast_scache_range(addr, addr + size);
if (!rm7k_tcache_init)
return;
a = addr & ~(tc_pagesize - 1);
end = (addr + size - 1) & ~(tc_pagesize - 1);
while(1) {
invalidate_tcache_page(a); /* Page_Invalidate_T */
if (a == end)
break;
a += tc_pagesize;
}
}
static void rm7k_sc_inv(unsigned long addr, unsigned long size)
{
unsigned long end, a;
pr_debug("rm7k_sc_inv[%08lx,%08lx]", addr, size);
/* Catch bad driver code */
BUG_ON(size == 0);
blast_inv_scache_range(addr, addr + size);
if (!rm7k_tcache_init)
return;
a = addr & ~(tc_pagesize - 1);
end = (addr + size - 1) & ~(tc_pagesize - 1);
while(1) {
invalidate_tcache_page(a); /* Page_Invalidate_T */
if (a == end)
break;
a += tc_pagesize;
}
}
static void blast_rm7k_tcache(void)
{
unsigned long start = CKSEG0ADDR(0);
unsigned long end = start + tcache_size;
write_c0_taglo(0);
while (start < end) {
cache_op(Page_Invalidate_T, start);
start += tc_pagesize;
}
}
/*
* This function is executed in uncached address space.
*/
static void __rm7k_tc_enable(void)
{
int i;
set_c0_config(RM7K_CONF_TE);
write_c0_taglo(0);
write_c0_taghi(0);
for (i = 0; i < tcache_size; i += tc_lsize)
cache_op(Index_Store_Tag_T, CKSEG0ADDR(i));
}
static void rm7k_tc_enable(void)
{
if (read_c0_config() & RM7K_CONF_TE)
return;
BUG_ON(tcache_size == 0);
run_uncached(__rm7k_tc_enable);
}
/*
* This function is executed in uncached address space.
*/
static void __rm7k_sc_enable(void)
{
int i;
set_c0_config(RM7K_CONF_SE);
write_c0_taglo(0);
write_c0_taghi(0);
for (i = 0; i < scache_size; i += sc_lsize)
cache_op(Index_Store_Tag_SD, CKSEG0ADDR(i));
}
static void rm7k_sc_enable(void)
{
if (read_c0_config() & RM7K_CONF_SE)
return;
pr_info("Enabling secondary cache...\n");
run_uncached(__rm7k_sc_enable);
if (rm7k_tcache_init)
rm7k_tc_enable();
}
static void rm7k_tc_disable(void)
{
unsigned long flags;
local_irq_save(flags);
blast_rm7k_tcache();
clear_c0_config(RM7K_CONF_TE);
local_irq_restore(flags);
}
static void rm7k_sc_disable(void)
{
clear_c0_config(RM7K_CONF_SE);
if (rm7k_tcache_init)
rm7k_tc_disable();
}
static struct bcache_ops rm7k_sc_ops = {
.bc_enable = rm7k_sc_enable,
.bc_disable = rm7k_sc_disable,
.bc_wback_inv = rm7k_sc_wback_inv,
.bc_inv = rm7k_sc_inv
};
/*
* This is a probing function like the one found in c-r4k.c, we look for the
* wrap around point with different addresses.
*/
static void __probe_tcache(void)
{
unsigned long flags, addr, begin, end, pow2;
begin = (unsigned long) &_stext;
begin &= ~((8 * 1024 * 1024) - 1);
end = begin + (8 * 1024 * 1024);
local_irq_save(flags);
set_c0_config(RM7K_CONF_TE);
/* Fill size-multiple lines with a valid tag */
pow2 = (256 * 1024);
for (addr = begin; addr <= end; addr = (begin + pow2)) {
unsigned long *p = (unsigned long *) addr;
__asm__ __volatile__("nop" : : "r" (*p));
pow2 <<= 1;
}
/* Load first line with a 0 tag, to check after */
write_c0_taglo(0);
write_c0_taghi(0);
cache_op(Index_Store_Tag_T, begin);
/* Look for the wrap-around */
pow2 = (512 * 1024);
for (addr = begin + (512 * 1024); addr <= end; addr = begin + pow2) {
cache_op(Index_Load_Tag_T, addr);
if (!read_c0_taglo())
break;
pow2 <<= 1;
}
addr -= begin;
tcache_size = addr;
clear_c0_config(RM7K_CONF_TE);
local_irq_restore(flags);
}
void rm7k_sc_init(void)
{
struct cpuinfo_mips *c = &current_cpu_data;
unsigned int config = read_c0_config();
if ((config & RM7K_CONF_SC))
return;
c->scache.linesz = sc_lsize;
c->scache.ways = 4;
c->scache.waybit= __ffs(scache_size / c->scache.ways);
c->scache.waysize = scache_size / c->scache.ways;
c->scache.sets = scache_size / (c->scache.linesz * c->scache.ways);
printk(KERN_INFO "Secondary cache size %dK, linesize %d bytes.\n",
(scache_size >> 10), sc_lsize);
if (!(config & RM7K_CONF_SE))
rm7k_sc_enable();
bcops = &rm7k_sc_ops;
/*
* While we're at it let's deal with the tertiary cache.
*/
rm7k_tcache_init = 0;
tcache_size = 0;
if (config & RM7K_CONF_TC)
return;
/*
* No efficient way to ask the hardware for the size of the tcache,
* so must probe for it.
*/
run_uncached(__probe_tcache);
rm7k_tc_enable();
rm7k_tcache_init = 1;
c->tcache.linesz = tc_lsize;
c->tcache.ways = 1;
pr_info("Tertiary cache size %ldK.\n", (tcache_size >> 10));
}
+40
View File
@@ -0,0 +1,40 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Micro-assembler generated tlb handler functions.
*
* Copyright (C) 2013 Broadcom Corporation.
*
* Based on mm/page-funcs.c
* Copyright (C) 2012 MIPS Technologies, Inc.
* Copyright (C) 2012 Ralf Baechle <ralf@linux-mips.org>
*/
#include <linux/export.h>
#include <asm/asm.h>
#include <asm/regdef.h>
#define FASTPATH_SIZE 128
LEAF(tlbmiss_handler_setup_pgd)
1: j 1b /* Dummy, will be replaced. */
.space 64
END(tlbmiss_handler_setup_pgd)
EXPORT(tlbmiss_handler_setup_pgd_end)
EXPORT_SYMBOL_GPL(tlbmiss_handler_setup_pgd)
LEAF(handle_tlbm)
.space FASTPATH_SIZE * 4
END(handle_tlbm)
EXPORT(handle_tlbm_end)
LEAF(handle_tlbs)
.space FASTPATH_SIZE * 4
END(handle_tlbs)
EXPORT(handle_tlbs_end)
LEAF(handle_tlbl)
.space FASTPATH_SIZE * 4
END(handle_tlbl)
EXPORT(handle_tlbl_end)
+248
View File
@@ -0,0 +1,248 @@
// 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();
}
+862
View File
@@ -0,0 +1,862 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1996 David S. Miller (davem@davemloft.net)
* Copyright (C) 1997, 1998, 1999, 2000 Ralf Baechle ralf@gnu.org
* Carsten Langgaard, carstenl@mips.com
* Copyright (C) 2002 MIPS Technologies, Inc. All rights reserved.
*/
#include <linux/cpu_pm.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/smp.h>
#include <linux/memblock.h>
#include <linux/minmax.h>
#include <linux/mm.h>
#include <linux/hugetlb.h>
#include <linux/export.h>
#include <linux/sort.h>
#include <asm/cpu.h>
#include <asm/cpu-type.h>
#include <asm/bootinfo.h>
#include <asm/hazards.h>
#include <asm/mmu_context.h>
#include <asm/tlb.h>
#include <asm/tlbdebug.h>
#include <asm/tlbex.h>
#include <asm/tlbmisc.h>
#include <asm/setup.h>
/*
* LOONGSON-2 has a 4 entry itlb which is a subset of jtlb, LOONGSON-3 has
* a 4 entry itlb and a 4 entry dtlb which are subsets of jtlb. Unfortunately,
* itlb/dtlb are not totally transparent to software.
*/
static inline void flush_micro_tlb(void)
{
switch (current_cpu_type()) {
case CPU_LOONGSON2EF:
write_c0_diag(LOONGSON_DIAG_ITLB);
break;
case CPU_LOONGSON64:
write_c0_diag(LOONGSON_DIAG_ITLB | LOONGSON_DIAG_DTLB);
break;
default:
break;
}
}
static inline void flush_micro_tlb_vm(struct vm_area_struct *vma)
{
if (vma->vm_flags & VM_EXEC)
flush_micro_tlb();
}
void local_flush_tlb_all(void)
{
unsigned long flags;
unsigned long old_ctx;
int entry, ftlbhighset;
local_irq_save(flags);
/* Save old context and create impossible VPN2 value */
old_ctx = read_c0_entryhi();
htw_stop();
write_c0_entrylo0(0);
write_c0_entrylo1(0);
entry = num_wired_entries();
/*
* Blast 'em all away.
* If there are any wired entries, fall back to iterating
*/
if (cpu_has_tlbinv && !entry) {
if (current_cpu_data.tlbsizevtlb) {
write_c0_index(0);
mtc0_tlbw_hazard();
tlbinvf(); /* invalidate VTLB */
}
ftlbhighset = current_cpu_data.tlbsizevtlb +
current_cpu_data.tlbsizeftlbsets;
for (entry = current_cpu_data.tlbsizevtlb;
entry < ftlbhighset;
entry++) {
write_c0_index(entry);
mtc0_tlbw_hazard();
tlbinvf(); /* invalidate one FTLB set */
}
} else {
while (entry < current_cpu_data.tlbsize) {
/* Make sure all entries differ. */
write_c0_entryhi(UNIQUE_ENTRYHI(entry));
write_c0_index(entry);
mtc0_tlbw_hazard();
tlb_write_indexed();
entry++;
}
}
tlbw_use_hazard();
write_c0_entryhi(old_ctx);
htw_start();
flush_micro_tlb();
local_irq_restore(flags);
}
EXPORT_SYMBOL(local_flush_tlb_all);
void local_flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
unsigned long end)
{
struct mm_struct *mm = vma->vm_mm;
int cpu = smp_processor_id();
if (cpu_context(cpu, mm) != 0) {
unsigned long size, flags;
local_irq_save(flags);
start = round_down(start, PAGE_SIZE << 1);
end = round_up(end, PAGE_SIZE << 1);
size = (end - start) >> (PAGE_SHIFT + 1);
if (size <= (current_cpu_data.tlbsizeftlbsets ?
current_cpu_data.tlbsize / 8 :
current_cpu_data.tlbsize / 2)) {
unsigned long old_entryhi, old_mmid;
int newpid = cpu_asid(cpu, mm);
old_entryhi = read_c0_entryhi();
if (cpu_has_mmid) {
old_mmid = read_c0_memorymapid();
write_c0_memorymapid(newpid);
}
htw_stop();
while (start < end) {
int idx;
if (cpu_has_mmid)
write_c0_entryhi(start);
else
write_c0_entryhi(start | newpid);
start += (PAGE_SIZE << 1);
mtc0_tlbw_hazard();
tlb_probe();
tlb_probe_hazard();
idx = read_c0_index();
write_c0_entrylo0(0);
write_c0_entrylo1(0);
if (idx < 0)
continue;
/* Make sure all entries differ. */
write_c0_entryhi(UNIQUE_ENTRYHI(idx));
mtc0_tlbw_hazard();
tlb_write_indexed();
}
tlbw_use_hazard();
write_c0_entryhi(old_entryhi);
if (cpu_has_mmid)
write_c0_memorymapid(old_mmid);
htw_start();
} else {
drop_mmu_context(mm);
}
flush_micro_tlb();
local_irq_restore(flags);
}
}
void local_flush_tlb_kernel_range(unsigned long start, unsigned long end)
{
unsigned long size, flags;
local_irq_save(flags);
size = (end - start + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
size = (size + 1) >> 1;
if (size <= (current_cpu_data.tlbsizeftlbsets ?
current_cpu_data.tlbsize / 8 :
current_cpu_data.tlbsize / 2)) {
int pid = read_c0_entryhi();
start &= (PAGE_MASK << 1);
end += ((PAGE_SIZE << 1) - 1);
end &= (PAGE_MASK << 1);
htw_stop();
while (start < end) {
int idx;
write_c0_entryhi(start);
start += (PAGE_SIZE << 1);
mtc0_tlbw_hazard();
tlb_probe();
tlb_probe_hazard();
idx = read_c0_index();
write_c0_entrylo0(0);
write_c0_entrylo1(0);
if (idx < 0)
continue;
/* Make sure all entries differ. */
write_c0_entryhi(UNIQUE_ENTRYHI(idx));
mtc0_tlbw_hazard();
tlb_write_indexed();
}
tlbw_use_hazard();
write_c0_entryhi(pid);
htw_start();
} else {
local_flush_tlb_all();
}
flush_micro_tlb();
local_irq_restore(flags);
}
void local_flush_tlb_page(struct vm_area_struct *vma, unsigned long page)
{
int cpu = smp_processor_id();
if (cpu_context(cpu, vma->vm_mm) != 0) {
unsigned long old_mmid;
unsigned long flags, old_entryhi;
int idx;
page &= (PAGE_MASK << 1);
local_irq_save(flags);
old_entryhi = read_c0_entryhi();
htw_stop();
if (cpu_has_mmid) {
old_mmid = read_c0_memorymapid();
write_c0_entryhi(page);
write_c0_memorymapid(cpu_asid(cpu, vma->vm_mm));
} else {
write_c0_entryhi(page | cpu_asid(cpu, vma->vm_mm));
}
mtc0_tlbw_hazard();
tlb_probe();
tlb_probe_hazard();
idx = read_c0_index();
write_c0_entrylo0(0);
write_c0_entrylo1(0);
if (idx < 0)
goto finish;
/* Make sure all entries differ. */
write_c0_entryhi(UNIQUE_ENTRYHI(idx));
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
finish:
write_c0_entryhi(old_entryhi);
if (cpu_has_mmid)
write_c0_memorymapid(old_mmid);
htw_start();
flush_micro_tlb_vm(vma);
local_irq_restore(flags);
}
}
/*
* This one is only used for pages with the global bit set so we don't care
* much about the ASID.
*/
void local_flush_tlb_one(unsigned long page)
{
unsigned long flags;
int oldpid, idx;
local_irq_save(flags);
oldpid = read_c0_entryhi();
htw_stop();
page &= (PAGE_MASK << 1);
write_c0_entryhi(page);
mtc0_tlbw_hazard();
tlb_probe();
tlb_probe_hazard();
idx = read_c0_index();
write_c0_entrylo0(0);
write_c0_entrylo1(0);
if (idx >= 0) {
/* Make sure all entries differ. */
write_c0_entryhi(UNIQUE_ENTRYHI(idx));
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
}
write_c0_entryhi(oldpid);
htw_start();
flush_micro_tlb();
local_irq_restore(flags);
}
/*
* We will need multiple versions of update_mmu_cache(), one that just
* updates the TLB with the new pte(s), and another which also checks
* for the R4k "end of page" hardware bug and does the needy.
*/
void __update_tlb(struct vm_area_struct * vma, unsigned long address, pte_t pte)
{
unsigned long flags;
pgd_t *pgdp;
p4d_t *p4dp;
pud_t *pudp;
pmd_t *pmdp;
pte_t *ptep, *ptemap = NULL;
int idx, pid;
/*
* Handle debugger faulting in for debuggee.
*/
if (current->active_mm != vma->vm_mm)
return;
local_irq_save(flags);
htw_stop();
address &= (PAGE_MASK << 1);
if (cpu_has_mmid) {
write_c0_entryhi(address);
} else {
pid = read_c0_entryhi() & cpu_asid_mask(&current_cpu_data);
write_c0_entryhi(address | pid);
}
pgdp = pgd_offset(vma->vm_mm, address);
mtc0_tlbw_hazard();
tlb_probe();
tlb_probe_hazard();
p4dp = p4d_offset(pgdp, address);
pudp = pud_offset(p4dp, address);
pmdp = pmd_offset(pudp, address);
idx = read_c0_index();
#ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT
/* this could be a huge page */
if (pmd_leaf(*pmdp)) {
unsigned long lo;
write_c0_pagemask(PM_HUGE_MASK);
ptep = (pte_t *)pmdp;
lo = pte_to_entrylo(pte_val(*ptep));
write_c0_entrylo0(lo);
write_c0_entrylo1(lo + (HPAGE_SIZE >> 7));
mtc0_tlbw_hazard();
if (idx < 0)
tlb_write_random();
else
tlb_write_indexed();
tlbw_use_hazard();
write_c0_pagemask(PM_DEFAULT_MASK);
} else
#endif
{
ptemap = ptep = pte_offset_map(pmdp, address);
/*
* update_mmu_cache() is called between pte_offset_map_lock()
* and pte_unmap_unlock(), so we can assume that ptep is not
* NULL here: and what should be done below if it were NULL?
*/
#if defined(CONFIG_PHYS_ADDR_T_64BIT) && defined(CONFIG_CPU_MIPS32)
#ifdef CONFIG_XPA
write_c0_entrylo0(pte_to_entrylo(ptep->pte_high));
if (cpu_has_xpa)
writex_c0_entrylo0(ptep->pte_low & _PFNX_MASK);
ptep++;
write_c0_entrylo1(pte_to_entrylo(ptep->pte_high));
if (cpu_has_xpa)
writex_c0_entrylo1(ptep->pte_low & _PFNX_MASK);
#else
write_c0_entrylo0(ptep->pte_high);
ptep++;
write_c0_entrylo1(ptep->pte_high);
#endif
#else
write_c0_entrylo0(pte_to_entrylo(pte_val(*ptep++)));
write_c0_entrylo1(pte_to_entrylo(pte_val(*ptep)));
#endif
mtc0_tlbw_hazard();
if (idx < 0)
tlb_write_random();
else
tlb_write_indexed();
}
tlbw_use_hazard();
htw_start();
flush_micro_tlb_vm(vma);
if (ptemap)
pte_unmap(ptemap);
local_irq_restore(flags);
}
void add_wired_entry(unsigned long entrylo0, unsigned long entrylo1,
unsigned long entryhi, unsigned long pagemask)
{
#ifdef CONFIG_XPA
panic("Broken for XPA kernels");
#else
unsigned int old_mmid;
unsigned long flags;
unsigned long wired;
unsigned long old_pagemask;
unsigned long old_ctx;
local_irq_save(flags);
if (cpu_has_mmid) {
old_mmid = read_c0_memorymapid();
write_c0_memorymapid(MMID_KERNEL_WIRED);
}
/* Save old context and create impossible VPN2 value */
old_ctx = read_c0_entryhi();
htw_stop();
old_pagemask = read_c0_pagemask();
wired = num_wired_entries();
write_c0_wired(wired + 1);
write_c0_index(wired);
tlbw_use_hazard(); /* What is the hazard here? */
write_c0_pagemask(pagemask);
write_c0_entryhi(entryhi);
write_c0_entrylo0(entrylo0);
write_c0_entrylo1(entrylo1);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
write_c0_entryhi(old_ctx);
if (cpu_has_mmid)
write_c0_memorymapid(old_mmid);
tlbw_use_hazard(); /* What is the hazard here? */
htw_start();
write_c0_pagemask(old_pagemask);
local_flush_tlb_all();
local_irq_restore(flags);
#endif
}
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
int has_transparent_hugepage(void)
{
static unsigned int mask = -1;
if (mask == -1) { /* first call comes during __init */
unsigned long flags;
local_irq_save(flags);
write_c0_pagemask(PM_HUGE_MASK);
back_to_back_c0_hazard();
mask = read_c0_pagemask();
write_c0_pagemask(PM_DEFAULT_MASK);
local_irq_restore(flags);
}
return mask == PM_HUGE_MASK;
}
EXPORT_SYMBOL(has_transparent_hugepage);
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
/*
* Used for loading TLB entries before trap_init() has started, when we
* don't actually want to add a wired entry which remains throughout the
* lifetime of the system
*/
int temp_tlb_entry;
#ifndef CONFIG_64BIT
__init int add_temporary_entry(unsigned long entrylo0, unsigned long entrylo1,
unsigned long entryhi, unsigned long pagemask)
{
int ret = 0;
unsigned long flags;
unsigned long wired;
unsigned long old_pagemask;
unsigned long old_ctx;
local_irq_save(flags);
/* Save old context and create impossible VPN2 value */
htw_stop();
old_ctx = read_c0_entryhi();
old_pagemask = read_c0_pagemask();
wired = num_wired_entries();
if (--temp_tlb_entry < wired) {
printk(KERN_WARNING
"No TLB space left for add_temporary_entry\n");
ret = -ENOSPC;
goto out;
}
write_c0_index(temp_tlb_entry);
write_c0_pagemask(pagemask);
write_c0_entryhi(entryhi);
write_c0_entrylo0(entrylo0);
write_c0_entrylo1(entrylo1);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlbw_use_hazard();
write_c0_entryhi(old_ctx);
write_c0_pagemask(old_pagemask);
htw_start();
out:
local_irq_restore(flags);
return ret;
}
#endif
static int ntlb;
static int __init set_ntlb(char *str)
{
get_option(&str, &ntlb);
return 1;
}
__setup("ntlb=", set_ntlb);
/* The start bit position of VPN2 and Mask in EntryHi/PageMask registers. */
#define VPN2_SHIFT 13
/* Read full EntryHi even with CONFIG_32BIT. */
static inline unsigned long long read_c0_entryhi_native(void)
{
return cpu_has_64bits ? read_c0_entryhi_64() : read_c0_entryhi();
}
/* Write full EntryHi even with CONFIG_32BIT. */
static inline void write_c0_entryhi_native(unsigned long long v)
{
if (cpu_has_64bits)
write_c0_entryhi_64(v);
else
write_c0_entryhi(v);
}
/* TLB entry state for uniquification. */
struct tlbent {
unsigned long long wired:1;
unsigned long long global:1;
unsigned long long asid:10;
unsigned long long vpn:51;
unsigned long long pagesz:5;
unsigned long long index:14;
};
/*
* Comparison function for TLB entry sorting. Place wired entries first,
* then global entries, then order by the increasing VPN/ASID and the
* decreasing page size. This lets us avoid clashes with wired entries
* easily and get entries for larger pages out of the way first.
*
* We could group bits so as to reduce the number of comparisons, but this
* is seldom executed and not performance-critical, so prefer legibility.
*/
static int r4k_entry_cmp(const void *a, const void *b)
{
struct tlbent ea = *(struct tlbent *)a, eb = *(struct tlbent *)b;
if (ea.wired > eb.wired)
return -1;
else if (ea.wired < eb.wired)
return 1;
else if (ea.global > eb.global)
return -1;
else if (ea.global < eb.global)
return 1;
else if (ea.vpn < eb.vpn)
return -1;
else if (ea.vpn > eb.vpn)
return 1;
else if (ea.asid < eb.asid)
return -1;
else if (ea.asid > eb.asid)
return 1;
else if (ea.pagesz > eb.pagesz)
return -1;
else if (ea.pagesz < eb.pagesz)
return 1;
else
return 0;
}
/*
* Fetch all the TLB entries. Mask individual VPN values retrieved with
* the corresponding page mask and ignoring any 1KiB extension as we'll
* be using 4KiB pages for uniquification.
*/
static void __ref r4k_tlb_uniquify_read(struct tlbent *tlb_vpns, int tlbsize)
{
int start = num_wired_entries();
unsigned long long vpn_mask;
bool global;
int i;
vpn_mask = GENMASK(current_cpu_data.vmbits - 1, VPN2_SHIFT);
vpn_mask |= cpu_has_64bits ? 3ULL << 62 : 1 << 31;
for (i = 0; i < tlbsize; i++) {
unsigned long long entryhi, vpn, mask, asid;
unsigned int pagesz;
write_c0_index(i);
mtc0_tlbr_hazard();
tlb_read();
tlb_read_hazard();
global = !!(read_c0_entrylo0() & ENTRYLO_G);
entryhi = read_c0_entryhi_native();
mask = read_c0_pagemask();
asid = entryhi & cpu_asid_mask(&current_cpu_data);
vpn = (entryhi & vpn_mask & ~mask) >> VPN2_SHIFT;
pagesz = ilog2((mask >> VPN2_SHIFT) + 1);
tlb_vpns[i].global = global;
tlb_vpns[i].asid = global ? 0 : asid;
tlb_vpns[i].vpn = vpn;
tlb_vpns[i].pagesz = pagesz;
tlb_vpns[i].wired = i < start;
tlb_vpns[i].index = i;
}
}
/*
* Write unique values to all but the wired TLB entries each, using
* the 4KiB page size. This size might not be supported with R6, but
* EHINV is mandatory for R6, so we won't ever be called in that case.
*
* A sorted table is supplied with any wired entries at the beginning,
* followed by any global entries, and then finally regular entries.
* We start at the VPN and ASID values of zero and only assign user
* addresses, therefore guaranteeing no clash with addresses produced
* by UNIQUE_ENTRYHI. We avoid any VPN values used by wired or global
* entries, by increasing the VPN value beyond the span of such entry.
*
* When a VPN/ASID clash is found with a regular entry we increment the
* ASID instead until no VPN/ASID clash has been found or the ASID space
* has been exhausted, in which case we increase the VPN value beyond
* the span of the largest clashing entry.
*
* We do not need to be concerned about FTLB or MMID configurations as
* those are required to implement the EHINV feature.
*/
static void __ref r4k_tlb_uniquify_write(struct tlbent *tlb_vpns, int tlbsize)
{
unsigned long long asid, vpn, vpn_size, pagesz;
int widx, gidx, idx, sidx, lidx, i;
vpn_size = 1ULL << (current_cpu_data.vmbits - VPN2_SHIFT);
pagesz = ilog2((PM_4K >> VPN2_SHIFT) + 1);
write_c0_pagemask(PM_4K);
write_c0_entrylo0(0);
write_c0_entrylo1(0);
asid = 0;
vpn = 0;
widx = 0;
gidx = 0;
for (sidx = 0; sidx < tlbsize && tlb_vpns[sidx].wired; sidx++)
;
for (lidx = sidx; lidx < tlbsize && tlb_vpns[lidx].global; lidx++)
;
idx = gidx = sidx + 1;
for (i = sidx; i < tlbsize; i++) {
unsigned long long entryhi, vpn_pagesz = 0;
while (1) {
if (WARN_ON(vpn >= vpn_size)) {
dump_tlb_all();
/* Pray local_flush_tlb_all() will cope. */
return;
}
/* VPN must be below the next wired entry. */
if (widx < sidx && vpn >= tlb_vpns[widx].vpn) {
vpn = max(vpn,
(tlb_vpns[widx].vpn +
(1ULL << tlb_vpns[widx].pagesz)));
asid = 0;
widx++;
continue;
}
/* VPN must be below the next global entry. */
if (gidx < lidx && vpn >= tlb_vpns[gidx].vpn) {
vpn = max(vpn,
(tlb_vpns[gidx].vpn +
(1ULL << tlb_vpns[gidx].pagesz)));
asid = 0;
gidx++;
continue;
}
/* Try to find a free ASID so as to conserve VPNs. */
if (idx < tlbsize && vpn == tlb_vpns[idx].vpn &&
asid == tlb_vpns[idx].asid) {
unsigned long long idx_pagesz;
idx_pagesz = tlb_vpns[idx].pagesz;
vpn_pagesz = max(vpn_pagesz, idx_pagesz);
do
idx++;
while (idx < tlbsize &&
vpn == tlb_vpns[idx].vpn &&
asid == tlb_vpns[idx].asid);
asid++;
if (asid > cpu_asid_mask(&current_cpu_data)) {
vpn += vpn_pagesz;
asid = 0;
vpn_pagesz = 0;
}
continue;
}
/* VPN mustn't be above the next regular entry. */
if (idx < tlbsize && vpn > tlb_vpns[idx].vpn) {
vpn = max(vpn,
(tlb_vpns[idx].vpn +
(1ULL << tlb_vpns[idx].pagesz)));
asid = 0;
idx++;
continue;
}
break;
}
entryhi = (vpn << VPN2_SHIFT) | asid;
write_c0_entryhi_native(entryhi);
write_c0_index(tlb_vpns[i].index);
mtc0_tlbw_hazard();
tlb_write_indexed();
tlb_vpns[i].asid = asid;
tlb_vpns[i].vpn = vpn;
tlb_vpns[i].pagesz = pagesz;
asid++;
if (asid > cpu_asid_mask(&current_cpu_data)) {
vpn += 1ULL << pagesz;
asid = 0;
}
}
}
/*
* Initialise all TLB entries with unique values that do not clash with
* what we have been handed over and what we'll be using ourselves.
*/
static void __ref r4k_tlb_uniquify(void)
{
int tlbsize = current_cpu_data.tlbsize;
bool use_slab = slab_is_available();
phys_addr_t tlb_vpn_size;
struct tlbent *tlb_vpns;
tlb_vpn_size = tlbsize * sizeof(*tlb_vpns);
tlb_vpns = (use_slab ?
kmalloc(tlb_vpn_size, GFP_ATOMIC) :
memblock_alloc_raw(tlb_vpn_size, sizeof(*tlb_vpns)));
if (WARN_ON(!tlb_vpns))
return; /* Pray local_flush_tlb_all() is good enough. */
htw_stop();
r4k_tlb_uniquify_read(tlb_vpns, tlbsize);
sort(tlb_vpns, tlbsize, sizeof(*tlb_vpns), r4k_entry_cmp, NULL);
r4k_tlb_uniquify_write(tlb_vpns, tlbsize);
write_c0_pagemask(PM_DEFAULT_MASK);
tlbw_use_hazard();
htw_start();
flush_micro_tlb();
if (use_slab)
kfree(tlb_vpns);
else
memblock_free(tlb_vpns, tlb_vpn_size);
}
/*
* Configure TLB (for init or after a CPU has been powered off).
*/
static void r4k_tlb_configure(void)
{
/*
* You should never change this register:
* - On R4600 1.7 the tlbp never hits for pages smaller than
* the value in the c0_pagemask register.
* - The entire mm handling assumes the c0_pagemask register to
* be set to fixed-size pages.
*/
write_c0_pagemask(PM_DEFAULT_MASK);
back_to_back_c0_hazard();
if (read_c0_pagemask() != PM_DEFAULT_MASK)
panic("MMU doesn't support PAGE_SIZE=0x%lx", PAGE_SIZE);
write_c0_wired(0);
if (current_cpu_type() == CPU_R10000 ||
current_cpu_type() == CPU_R12000 ||
current_cpu_type() == CPU_R14000 ||
current_cpu_type() == CPU_R16000)
write_c0_framemask(0);
if (cpu_has_rixi) {
/*
* Enable the no read, no exec bits, and enable large physical
* address.
*/
#ifdef CONFIG_64BIT
set_c0_pagegrain(PG_RIE | PG_XIE | PG_ELPA);
#else
set_c0_pagegrain(PG_RIE | PG_XIE);
#endif
}
temp_tlb_entry = current_cpu_data.tlbsize - 1;
/* From this point on the ARC firmware is dead. */
if (!cpu_has_tlbinv)
r4k_tlb_uniquify();
local_flush_tlb_all();
/* Did I tell you that ARC SUCKS? */
}
void tlb_init(void)
{
r4k_tlb_configure();
if (ntlb) {
if (ntlb > 1 && ntlb <= current_cpu_data.tlbsize) {
int wired = current_cpu_data.tlbsize - ntlb;
write_c0_wired(wired);
write_c0_index(wired-1);
printk("Restricting TLB to %d entries\n", ntlb);
} else
printk("Ignoring invalid argument ntlb=%d\n", ntlb);
}
build_tlb_refill_handler();
}
static int r4k_tlb_pm_notifier(struct notifier_block *self, unsigned long cmd,
void *v)
{
switch (cmd) {
case CPU_PM_ENTER_FAILED:
case CPU_PM_EXIT:
r4k_tlb_configure();
break;
}
return NOTIFY_OK;
}
static struct notifier_block r4k_tlb_pm_notifier_block = {
.notifier_call = r4k_tlb_pm_notifier,
};
static int __init r4k_tlb_init_pm(void)
{
return cpu_pm_register_notifier(&r4k_tlb_pm_notifier_block);
}
arch_initcall(r4k_tlb_init_pm);
+28
View File
@@ -0,0 +1,28 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (C) 1999 Ralf Baechle
* Copyright (C) 1999 Silicon Graphics, Inc.
*/
#include <asm/mipsregs.h>
#include <asm/regdef.h>
#include <asm/stackframe.h>
.macro tlb_do_page_fault, write
NESTED(tlb_do_page_fault_\write, PT_SIZE, sp)
.cfi_signal_frame
SAVE_ALL docfi=1
MFC0 a2, CP0_BADVADDR
KMODE
move a0, sp
REG_S a2, PT_BVADDR(sp)
li a1, \write
jal do_page_fault
j ret_from_exception
END(tlb_do_page_fault_\write)
.endm
tlb_do_page_fault 0
tlb_do_page_fault 1
File diff suppressed because it is too large Load Diff
+232
View File
@@ -0,0 +1,232 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* A small micro-assembler. It is intentionally kept simple, does only
* support a subset of instructions, and does not try to hide pipeline
* effects like branch delay slots.
*
* Copyright (C) 2004, 2005, 2006, 2008 Thiemo Seufer
* Copyright (C) 2005, 2007 Maciej W. Rozycki
* Copyright (C) 2006 Ralf Baechle (ralf@linux-mips.org)
* Copyright (C) 2012, 2013 MIPS Technologies, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <asm/inst.h>
#include <asm/elf.h>
#include <asm/bugs.h>
#include <asm/uasm.h>
#define RS_MASK 0x1f
#define RS_SH 16
#define RT_MASK 0x1f
#define RT_SH 21
#define SCIMM_MASK 0x3ff
#define SCIMM_SH 16
/* This macro sets the non-variable bits of an instruction. */
#define M(a, b, c, d, e, f) \
((a) << OP_SH \
| (b) << RT_SH \
| (c) << RS_SH \
| (d) << RD_SH \
| (e) << RE_SH \
| (f) << FUNC_SH)
#include "uasm.c"
static const struct insn insn_table_MM[insn_invalid] = {
[insn_addu] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_addu32_op), RT | RS | RD},
[insn_addiu] = {M(mm_addiu32_op, 0, 0, 0, 0, 0), RT | RS | SIMM},
[insn_and] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_and_op), RT | RS | RD},
[insn_andi] = {M(mm_andi32_op, 0, 0, 0, 0, 0), RT | RS | UIMM},
[insn_beq] = {M(mm_beq32_op, 0, 0, 0, 0, 0), RS | RT | BIMM},
[insn_beql] = {0, 0},
[insn_bgez] = {M(mm_pool32i_op, mm_bgez_op, 0, 0, 0, 0), RS | BIMM},
[insn_bgezl] = {0, 0},
[insn_bltz] = {M(mm_pool32i_op, mm_bltz_op, 0, 0, 0, 0), RS | BIMM},
[insn_bltzl] = {0, 0},
[insn_bne] = {M(mm_bne32_op, 0, 0, 0, 0, 0), RT | RS | BIMM},
[insn_cache] = {M(mm_pool32b_op, 0, 0, mm_cache_func, 0, 0), RT | RS | SIMM},
[insn_cfc1] = {M(mm_pool32f_op, 0, 0, 0, mm_cfc1_op, mm_32f_73_op), RT | RS},
[insn_cfcmsa] = {M(mm_pool32s_op, 0, msa_cfc_op, 0, 0, mm_32s_elm_op), RD | RE},
[insn_ctc1] = {M(mm_pool32f_op, 0, 0, 0, mm_ctc1_op, mm_32f_73_op), RT | RS},
[insn_ctcmsa] = {M(mm_pool32s_op, 0, msa_ctc_op, 0, 0, mm_32s_elm_op), RD | RE},
[insn_daddu] = {0, 0},
[insn_daddiu] = {0, 0},
[insn_di] = {M(mm_pool32a_op, 0, 0, 0, mm_di_op, mm_pool32axf_op), RS},
[insn_divu] = {M(mm_pool32a_op, 0, 0, 0, mm_divu_op, mm_pool32axf_op), RT | RS},
[insn_dmfc0] = {0, 0},
[insn_dmtc0] = {0, 0},
[insn_dsll] = {0, 0},
[insn_dsll32] = {0, 0},
[insn_dsra] = {0, 0},
[insn_dsrl] = {0, 0},
[insn_dsrl32] = {0, 0},
[insn_drotr] = {0, 0},
[insn_drotr32] = {0, 0},
[insn_dsubu] = {0, 0},
[insn_eret] = {M(mm_pool32a_op, 0, 0, 0, mm_eret_op, mm_pool32axf_op), 0},
[insn_ins] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_ins_op), RT | RS | RD | RE},
[insn_ext] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_ext_op), RT | RS | RD | RE},
[insn_j] = {M(mm_j32_op, 0, 0, 0, 0, 0), JIMM},
[insn_jal] = {M(mm_jal32_op, 0, 0, 0, 0, 0), JIMM},
[insn_jalr] = {M(mm_pool32a_op, 0, 0, 0, mm_jalr_op, mm_pool32axf_op), RT | RS},
[insn_jr] = {M(mm_pool32a_op, 0, 0, 0, mm_jalr_op, mm_pool32axf_op), RS},
[insn_lb] = {M(mm_lb32_op, 0, 0, 0, 0, 0), RT | RS | SIMM},
[insn_ld] = {0, 0},
[insn_lh] = {M(mm_lh32_op, 0, 0, 0, 0, 0), RT | RS | SIMM},
[insn_ll] = {M(mm_pool32c_op, 0, 0, (mm_ll_func << 1), 0, 0), RS | RT | SIMM},
[insn_lld] = {0, 0},
[insn_lui] = {M(mm_pool32i_op, mm_lui_op, 0, 0, 0, 0), RS | SIMM},
[insn_lw] = {M(mm_lw32_op, 0, 0, 0, 0, 0), RT | RS | SIMM},
[insn_mfc0] = {M(mm_pool32a_op, 0, 0, 0, mm_mfc0_op, mm_pool32axf_op), RT | RS | RD},
[insn_mfhi] = {M(mm_pool32a_op, 0, 0, 0, mm_mfhi32_op, mm_pool32axf_op), RS},
[insn_mflo] = {M(mm_pool32a_op, 0, 0, 0, mm_mflo32_op, mm_pool32axf_op), RS},
[insn_mtc0] = {M(mm_pool32a_op, 0, 0, 0, mm_mtc0_op, mm_pool32axf_op), RT | RS | RD},
[insn_mthi] = {M(mm_pool32a_op, 0, 0, 0, mm_mthi32_op, mm_pool32axf_op), RS},
[insn_mtlo] = {M(mm_pool32a_op, 0, 0, 0, mm_mtlo32_op, mm_pool32axf_op), RS},
[insn_mul] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_mul_op), RT | RS | RD},
[insn_or] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_or32_op), RT | RS | RD},
[insn_ori] = {M(mm_ori32_op, 0, 0, 0, 0, 0), RT | RS | UIMM},
[insn_pref] = {M(mm_pool32c_op, 0, 0, (mm_pref_func << 1), 0, 0), RT | RS | SIMM},
[insn_rfe] = {0, 0},
[insn_sc] = {M(mm_pool32c_op, 0, 0, (mm_sc_func << 1), 0, 0), RT | RS | SIMM},
[insn_scd] = {0, 0},
[insn_sd] = {0, 0},
[insn_sll] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_sll32_op), RT | RS | RD},
[insn_sllv] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_sllv32_op), RT | RS | RD},
[insn_slt] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_slt_op), RT | RS | RD},
[insn_sltiu] = {M(mm_sltiu32_op, 0, 0, 0, 0, 0), RT | RS | SIMM},
[insn_sltu] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_sltu_op), RT | RS | RD},
[insn_sra] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_sra_op), RT | RS | RD},
[insn_srav] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_srav_op), RT | RS | RD},
[insn_srl] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_srl32_op), RT | RS | RD},
[insn_srlv] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_srlv32_op), RT | RS | RD},
[insn_rotr] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_rotr_op), RT | RS | RD},
[insn_subu] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_subu32_op), RT | RS | RD},
[insn_sw] = {M(mm_sw32_op, 0, 0, 0, 0, 0), RT | RS | SIMM},
[insn_sync] = {M(mm_pool32a_op, 0, 0, 0, mm_sync_op, mm_pool32axf_op), RS},
[insn_tlbp] = {M(mm_pool32a_op, 0, 0, 0, mm_tlbp_op, mm_pool32axf_op), 0},
[insn_tlbr] = {M(mm_pool32a_op, 0, 0, 0, mm_tlbr_op, mm_pool32axf_op), 0},
[insn_tlbwi] = {M(mm_pool32a_op, 0, 0, 0, mm_tlbwi_op, mm_pool32axf_op), 0},
[insn_tlbwr] = {M(mm_pool32a_op, 0, 0, 0, mm_tlbwr_op, mm_pool32axf_op), 0},
[insn_wait] = {M(mm_pool32a_op, 0, 0, 0, mm_wait_op, mm_pool32axf_op), SCIMM},
[insn_wsbh] = {M(mm_pool32a_op, 0, 0, 0, mm_wsbh_op, mm_pool32axf_op), RT | RS},
[insn_xor] = {M(mm_pool32a_op, 0, 0, 0, 0, mm_xor32_op), RT | RS | RD},
[insn_xori] = {M(mm_xori32_op, 0, 0, 0, 0, 0), RT | RS | UIMM},
[insn_dins] = {0, 0},
[insn_dinsm] = {0, 0},
[insn_syscall] = {M(mm_pool32a_op, 0, 0, 0, mm_syscall_op, mm_pool32axf_op), SCIMM},
[insn_bbit0] = {0, 0},
[insn_bbit1] = {0, 0},
[insn_lwx] = {0, 0},
[insn_ldx] = {0, 0},
};
#undef M
static inline u32 build_bimm(s32 arg)
{
WARN(arg > 0xffff || arg < -0x10000,
KERN_WARNING "Micro-assembler field overflow\n");
WARN(arg & 0x3, KERN_WARNING "Invalid micro-assembler branch target\n");
return ((arg < 0) ? (1 << 15) : 0) | ((arg >> 1) & 0x7fff);
}
static inline u32 build_jimm(u32 arg)
{
WARN(arg & ~((JIMM_MASK << 2) | 1),
KERN_WARNING "Micro-assembler field overflow\n");
return (arg >> 1) & JIMM_MASK;
}
/*
* The order of opcode arguments is implicitly left to right,
* starting with RS and ending with FUNC or IMM.
*/
static void build_insn(u32 **buf, enum opcode opc, ...)
{
const struct insn *ip;
va_list ap;
u32 op;
if (opc < 0 || opc >= insn_invalid ||
(opc == insn_daddiu && r4k_daddiu_bug()) ||
(insn_table_MM[opc].match == 0 && insn_table_MM[opc].fields == 0))
panic("Unsupported Micro-assembler instruction %d", opc);
ip = &insn_table_MM[opc];
op = ip->match;
va_start(ap, opc);
if (ip->fields & RS) {
if (opc == insn_mfc0 || opc == insn_mtc0 ||
opc == insn_cfc1 || opc == insn_ctc1)
op |= build_rt(va_arg(ap, u32));
else
op |= build_rs(va_arg(ap, u32));
}
if (ip->fields & RT) {
if (opc == insn_mfc0 || opc == insn_mtc0 ||
opc == insn_cfc1 || opc == insn_ctc1)
op |= build_rs(va_arg(ap, u32));
else
op |= build_rt(va_arg(ap, u32));
}
if (ip->fields & RD)
op |= build_rd(va_arg(ap, u32));
if (ip->fields & RE)
op |= build_re(va_arg(ap, u32));
if (ip->fields & SIMM)
op |= build_simm(va_arg(ap, s32));
if (ip->fields & UIMM)
op |= build_uimm(va_arg(ap, u32));
if (ip->fields & BIMM)
op |= build_bimm(va_arg(ap, s32));
if (ip->fields & JIMM)
op |= build_jimm(va_arg(ap, u32));
if (ip->fields & FUNC)
op |= build_func(va_arg(ap, u32));
if (ip->fields & SET)
op |= build_set(va_arg(ap, u32));
if (ip->fields & SCIMM)
op |= build_scimm(va_arg(ap, u32));
va_end(ap);
#ifdef CONFIG_CPU_LITTLE_ENDIAN
**buf = ((op & 0xffff) << 16) | (op >> 16);
#else
**buf = op;
#endif
(*buf)++;
}
static inline void
__resolve_relocs(struct uasm_reloc *rel, struct uasm_label *lab)
{
long laddr = (long)lab->addr;
long raddr = (long)rel->addr;
switch (rel->type) {
case R_MIPS_PC16:
#ifdef CONFIG_CPU_LITTLE_ENDIAN
*rel->addr |= (build_bimm(laddr - (raddr + 4)) << 16);
#else
*rel->addr |= build_bimm(laddr - (raddr + 4));
#endif
break;
default:
panic("Unsupported Micro-assembler relocation %d",
rel->type);
}
}
+292
View File
@@ -0,0 +1,292 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* A small micro-assembler. It is intentionally kept simple, does only
* support a subset of instructions, and does not try to hide pipeline
* effects like branch delay slots.
*
* Copyright (C) 2004, 2005, 2006, 2008 Thiemo Seufer
* Copyright (C) 2005, 2007 Maciej W. Rozycki
* Copyright (C) 2006 Ralf Baechle (ralf@linux-mips.org)
* Copyright (C) 2012, 2013 MIPS Technologies, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <asm/inst.h>
#include <asm/elf.h>
#include <asm/bugs.h>
#include <asm/uasm.h>
#define RS_MASK 0x1f
#define RS_SH 21
#define RT_MASK 0x1f
#define RT_SH 16
#define SCIMM_MASK 0xfffff
#define SCIMM_SH 6
/* This macro sets the non-variable bits of an instruction. */
#define M(a, b, c, d, e, f) \
((a) << OP_SH \
| (b) << RS_SH \
| (c) << RT_SH \
| (d) << RD_SH \
| (e) << RE_SH \
| (f) << FUNC_SH)
/* This macro sets the non-variable bits of an R6 instruction. */
#define M6(a, b, c, d, e) \
((a) << OP_SH \
| (b) << RS_SH \
| (c) << RT_SH \
| (d) << SIMM9_SH \
| (e) << FUNC_SH)
#include "uasm.c"
static const struct insn insn_table[insn_invalid] = {
[insn_addiu] = {M(addiu_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_addu] = {M(spec_op, 0, 0, 0, 0, addu_op), RS | RT | RD},
[insn_and] = {M(spec_op, 0, 0, 0, 0, and_op), RS | RT | RD},
[insn_andi] = {M(andi_op, 0, 0, 0, 0, 0), RS | RT | UIMM},
[insn_bbit0] = {M(lwc2_op, 0, 0, 0, 0, 0), RS | RT | BIMM},
[insn_bbit1] = {M(swc2_op, 0, 0, 0, 0, 0), RS | RT | BIMM},
[insn_beq] = {M(beq_op, 0, 0, 0, 0, 0), RS | RT | BIMM},
[insn_beql] = {M(beql_op, 0, 0, 0, 0, 0), RS | RT | BIMM},
[insn_bgez] = {M(bcond_op, 0, bgez_op, 0, 0, 0), RS | BIMM},
[insn_bgezl] = {M(bcond_op, 0, bgezl_op, 0, 0, 0), RS | BIMM},
[insn_bgtz] = {M(bgtz_op, 0, 0, 0, 0, 0), RS | BIMM},
[insn_blez] = {M(blez_op, 0, 0, 0, 0, 0), RS | BIMM},
[insn_bltz] = {M(bcond_op, 0, bltz_op, 0, 0, 0), RS | BIMM},
[insn_bltzl] = {M(bcond_op, 0, bltzl_op, 0, 0, 0), RS | BIMM},
[insn_bne] = {M(bne_op, 0, 0, 0, 0, 0), RS | RT | BIMM},
[insn_break] = {M(spec_op, 0, 0, 0, 0, break_op), SCIMM},
#ifndef CONFIG_CPU_MIPSR6
[insn_cache] = {M(cache_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
#else
[insn_cache] = {M6(spec3_op, 0, 0, 0, cache6_op), RS | RT | SIMM9},
#endif
[insn_cfc1] = {M(cop1_op, cfc_op, 0, 0, 0, 0), RT | RD},
[insn_cfcmsa] = {M(msa_op, 0, msa_cfc_op, 0, 0, msa_elm_op), RD | RE},
[insn_ctc1] = {M(cop1_op, ctc_op, 0, 0, 0, 0), RT | RD},
[insn_ctcmsa] = {M(msa_op, 0, msa_ctc_op, 0, 0, msa_elm_op), RD | RE},
[insn_daddiu] = {M(daddiu_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_daddu] = {M(spec_op, 0, 0, 0, 0, daddu_op), RS | RT | RD},
[insn_ddivu] = {M(spec_op, 0, 0, 0, 0, ddivu_op), RS | RT},
[insn_ddivu_r6] = {M(spec_op, 0, 0, 0, ddivu_ddivu6_op, ddivu_op),
RS | RT | RD},
[insn_di] = {M(cop0_op, mfmc0_op, 0, 12, 0, 0), RT},
[insn_dins] = {M(spec3_op, 0, 0, 0, 0, dins_op), RS | RT | RD | RE},
[insn_dinsm] = {M(spec3_op, 0, 0, 0, 0, dinsm_op), RS | RT | RD | RE},
[insn_dinsu] = {M(spec3_op, 0, 0, 0, 0, dinsu_op), RS | RT | RD | RE},
[insn_divu] = {M(spec_op, 0, 0, 0, 0, divu_op), RS | RT},
[insn_divu_r6] = {M(spec_op, 0, 0, 0, divu_divu6_op, divu_op),
RS | RT | RD},
[insn_dmfc0] = {M(cop0_op, dmfc_op, 0, 0, 0, 0), RT | RD | SET},
[insn_dmodu] = {M(spec_op, 0, 0, 0, ddivu_dmodu_op, ddivu_op),
RS | RT | RD},
[insn_dmtc0] = {M(cop0_op, dmtc_op, 0, 0, 0, 0), RT | RD | SET},
[insn_dmultu] = {M(spec_op, 0, 0, 0, 0, dmultu_op), RS | RT},
[insn_dmulu] = {M(spec_op, 0, 0, 0, dmultu_dmulu_op, dmultu_op),
RS | RT | RD},
[insn_drotr] = {M(spec_op, 1, 0, 0, 0, dsrl_op), RT | RD | RE},
[insn_drotr32] = {M(spec_op, 1, 0, 0, 0, dsrl32_op), RT | RD | RE},
[insn_dsbh] = {M(spec3_op, 0, 0, 0, dsbh_op, dbshfl_op), RT | RD},
[insn_dshd] = {M(spec3_op, 0, 0, 0, dshd_op, dbshfl_op), RT | RD},
[insn_dsll] = {M(spec_op, 0, 0, 0, 0, dsll_op), RT | RD | RE},
[insn_dsll32] = {M(spec_op, 0, 0, 0, 0, dsll32_op), RT | RD | RE},
[insn_dsllv] = {M(spec_op, 0, 0, 0, 0, dsllv_op), RS | RT | RD},
[insn_dsra] = {M(spec_op, 0, 0, 0, 0, dsra_op), RT | RD | RE},
[insn_dsra32] = {M(spec_op, 0, 0, 0, 0, dsra32_op), RT | RD | RE},
[insn_dsrav] = {M(spec_op, 0, 0, 0, 0, dsrav_op), RS | RT | RD},
[insn_dsrl] = {M(spec_op, 0, 0, 0, 0, dsrl_op), RT | RD | RE},
[insn_dsrl32] = {M(spec_op, 0, 0, 0, 0, dsrl32_op), RT | RD | RE},
[insn_dsrlv] = {M(spec_op, 0, 0, 0, 0, dsrlv_op), RS | RT | RD},
[insn_dsubu] = {M(spec_op, 0, 0, 0, 0, dsubu_op), RS | RT | RD},
[insn_eret] = {M(cop0_op, cop_op, 0, 0, 0, eret_op), 0},
[insn_ext] = {M(spec3_op, 0, 0, 0, 0, ext_op), RS | RT | RD | RE},
[insn_ins] = {M(spec3_op, 0, 0, 0, 0, ins_op), RS | RT | RD | RE},
[insn_j] = {M(j_op, 0, 0, 0, 0, 0), JIMM},
[insn_jal] = {M(jal_op, 0, 0, 0, 0, 0), JIMM},
[insn_jalr] = {M(spec_op, 0, 0, 0, 0, jalr_op), RS | RD},
#ifndef CONFIG_CPU_MIPSR6
[insn_jr] = {M(spec_op, 0, 0, 0, 0, jr_op), RS},
#else
[insn_jr] = {M(spec_op, 0, 0, 0, 0, jalr_op), RS},
#endif
[insn_lb] = {M(lb_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_lbu] = {M(lbu_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_ld] = {M(ld_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_lddir] = {M(lwc2_op, 0, 0, 0, lddir_op, mult_op), RS | RT | RD},
[insn_ldpte] = {M(lwc2_op, 0, 0, 0, ldpte_op, mult_op), RS | RD},
[insn_ldx] = {M(spec3_op, 0, 0, 0, ldx_op, lx_op), RS | RT | RD},
[insn_lh] = {M(lh_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_lhu] = {M(lhu_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
#ifndef CONFIG_CPU_MIPSR6
[insn_ll] = {M(ll_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_lld] = {M(lld_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
#else
[insn_ll] = {M6(spec3_op, 0, 0, 0, ll6_op), RS | RT | SIMM9},
[insn_lld] = {M6(spec3_op, 0, 0, 0, lld6_op), RS | RT | SIMM9},
#endif
[insn_lui] = {M(lui_op, 0, 0, 0, 0, 0), RT | SIMM},
[insn_lw] = {M(lw_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_lwu] = {M(lwu_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_lwx] = {M(spec3_op, 0, 0, 0, lwx_op, lx_op), RS | RT | RD},
[insn_mfc0] = {M(cop0_op, mfc_op, 0, 0, 0, 0), RT | RD | SET},
[insn_mfhc0] = {M(cop0_op, mfhc0_op, 0, 0, 0, 0), RT | RD | SET},
[insn_mfhi] = {M(spec_op, 0, 0, 0, 0, mfhi_op), RD},
[insn_mflo] = {M(spec_op, 0, 0, 0, 0, mflo_op), RD},
[insn_modu] = {M(spec_op, 0, 0, 0, divu_modu_op, divu_op),
RS | RT | RD},
[insn_movn] = {M(spec_op, 0, 0, 0, 0, movn_op), RS | RT | RD},
[insn_movz] = {M(spec_op, 0, 0, 0, 0, movz_op), RS | RT | RD},
[insn_mtc0] = {M(cop0_op, mtc_op, 0, 0, 0, 0), RT | RD | SET},
[insn_mthc0] = {M(cop0_op, mthc0_op, 0, 0, 0, 0), RT | RD | SET},
[insn_mthi] = {M(spec_op, 0, 0, 0, 0, mthi_op), RS},
[insn_mtlo] = {M(spec_op, 0, 0, 0, 0, mtlo_op), RS},
[insn_mulu] = {M(spec_op, 0, 0, 0, multu_mulu_op, multu_op),
RS | RT | RD},
[insn_muhu] = {M(spec_op, 0, 0, 0, multu_muhu_op, multu_op),
RS | RT | RD},
#ifndef CONFIG_CPU_MIPSR6
[insn_mul] = {M(spec2_op, 0, 0, 0, 0, mul_op), RS | RT | RD},
#else
[insn_mul] = {M(spec_op, 0, 0, 0, mult_mul_op, mult_op), RS | RT | RD},
#endif
[insn_multu] = {M(spec_op, 0, 0, 0, 0, multu_op), RS | RT},
[insn_nor] = {M(spec_op, 0, 0, 0, 0, nor_op), RS | RT | RD},
[insn_or] = {M(spec_op, 0, 0, 0, 0, or_op), RS | RT | RD},
[insn_ori] = {M(ori_op, 0, 0, 0, 0, 0), RS | RT | UIMM},
#ifndef CONFIG_CPU_MIPSR6
[insn_pref] = {M(pref_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
#else
[insn_pref] = {M6(spec3_op, 0, 0, 0, pref6_op), RS | RT | SIMM9},
#endif
[insn_rfe] = {M(cop0_op, cop_op, 0, 0, 0, rfe_op), 0},
[insn_rotr] = {M(spec_op, 1, 0, 0, 0, srl_op), RT | RD | RE},
[insn_sb] = {M(sb_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
#ifndef CONFIG_CPU_MIPSR6
[insn_sc] = {M(sc_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_scd] = {M(scd_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
#else
[insn_sc] = {M6(spec3_op, 0, 0, 0, sc6_op), RS | RT | SIMM9},
[insn_scd] = {M6(spec3_op, 0, 0, 0, scd6_op), RS | RT | SIMM9},
#endif
[insn_sd] = {M(sd_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_seleqz] = {M(spec_op, 0, 0, 0, 0, seleqz_op), RS | RT | RD},
[insn_selnez] = {M(spec_op, 0, 0, 0, 0, selnez_op), RS | RT | RD},
[insn_sh] = {M(sh_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_sll] = {M(spec_op, 0, 0, 0, 0, sll_op), RT | RD | RE},
[insn_sllv] = {M(spec_op, 0, 0, 0, 0, sllv_op), RS | RT | RD},
[insn_slt] = {M(spec_op, 0, 0, 0, 0, slt_op), RS | RT | RD},
[insn_slti] = {M(slti_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_sltiu] = {M(sltiu_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_sltu] = {M(spec_op, 0, 0, 0, 0, sltu_op), RS | RT | RD},
[insn_sra] = {M(spec_op, 0, 0, 0, 0, sra_op), RT | RD | RE},
[insn_srav] = {M(spec_op, 0, 0, 0, 0, srav_op), RS | RT | RD},
[insn_srl] = {M(spec_op, 0, 0, 0, 0, srl_op), RT | RD | RE},
[insn_srlv] = {M(spec_op, 0, 0, 0, 0, srlv_op), RS | RT | RD},
[insn_subu] = {M(spec_op, 0, 0, 0, 0, subu_op), RS | RT | RD},
[insn_sw] = {M(sw_op, 0, 0, 0, 0, 0), RS | RT | SIMM},
[insn_sync] = {M(spec_op, 0, 0, 0, 0, sync_op), RE},
[insn_syscall] = {M(spec_op, 0, 0, 0, 0, syscall_op), SCIMM},
[insn_tlbp] = {M(cop0_op, cop_op, 0, 0, 0, tlbp_op), 0},
[insn_tlbr] = {M(cop0_op, cop_op, 0, 0, 0, tlbr_op), 0},
[insn_tlbwi] = {M(cop0_op, cop_op, 0, 0, 0, tlbwi_op), 0},
[insn_tlbwr] = {M(cop0_op, cop_op, 0, 0, 0, tlbwr_op), 0},
[insn_wait] = {M(cop0_op, cop_op, 0, 0, 0, wait_op), SCIMM},
[insn_wsbh] = {M(spec3_op, 0, 0, 0, wsbh_op, bshfl_op), RT | RD},
[insn_xor] = {M(spec_op, 0, 0, 0, 0, xor_op), RS | RT | RD},
[insn_xori] = {M(xori_op, 0, 0, 0, 0, 0), RS | RT | UIMM},
[insn_yield] = {M(spec3_op, 0, 0, 0, 0, yield_op), RS | RD},
};
#undef M
static inline u32 build_bimm(s32 arg)
{
WARN(arg > 0x1ffff || arg < -0x20000,
KERN_WARNING "Micro-assembler field overflow\n");
WARN(arg & 0x3, KERN_WARNING "Invalid micro-assembler branch target\n");
return ((arg < 0) ? (1 << 15) : 0) | ((arg >> 2) & 0x7fff);
}
static inline u32 build_jimm(u32 arg)
{
WARN(arg & ~(JIMM_MASK << 2),
KERN_WARNING "Micro-assembler field overflow\n");
return (arg >> 2) & JIMM_MASK;
}
/*
* The order of opcode arguments is implicitly left to right,
* starting with RS and ending with FUNC or IMM.
*/
static void build_insn(u32 **buf, enum opcode opc, ...)
{
const struct insn *ip;
va_list ap;
u32 op;
if (opc < 0 || opc >= insn_invalid ||
(opc == insn_daddiu && r4k_daddiu_bug()) ||
(insn_table[opc].match == 0 && insn_table[opc].fields == 0))
panic("Unsupported Micro-assembler instruction %d", opc);
ip = &insn_table[opc];
op = ip->match;
va_start(ap, opc);
if (ip->fields & RS)
op |= build_rs(va_arg(ap, u32));
if (ip->fields & RT)
op |= build_rt(va_arg(ap, u32));
if (ip->fields & RD)
op |= build_rd(va_arg(ap, u32));
if (ip->fields & RE)
op |= build_re(va_arg(ap, u32));
if (ip->fields & SIMM)
op |= build_simm(va_arg(ap, s32));
if (ip->fields & UIMM)
op |= build_uimm(va_arg(ap, u32));
if (ip->fields & BIMM)
op |= build_bimm(va_arg(ap, s32));
if (ip->fields & JIMM)
op |= build_jimm(va_arg(ap, u32));
if (ip->fields & FUNC)
op |= build_func(va_arg(ap, u32));
if (ip->fields & SET)
op |= build_set(va_arg(ap, u32));
if (ip->fields & SCIMM)
op |= build_scimm(va_arg(ap, u32));
if (ip->fields & SIMM9)
op |= build_scimm9(va_arg(ap, u32));
va_end(ap);
**buf = op;
(*buf)++;
}
static inline void
__resolve_relocs(struct uasm_reloc *rel, struct uasm_label *lab)
{
long laddr = (long)lab->addr;
long raddr = (long)rel->addr;
switch (rel->type) {
case R_MIPS_PC16:
*rel->addr |= build_bimm(laddr - (raddr + 4));
break;
default:
panic("Unsupported Micro-assembler relocation %d",
rel->type);
}
}
+644
View File
@@ -0,0 +1,644 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* A small micro-assembler. It is intentionally kept simple, does only
* support a subset of instructions, and does not try to hide pipeline
* effects like branch delay slots.
*
* Copyright (C) 2004, 2005, 2006, 2008 Thiemo Seufer
* Copyright (C) 2005, 2007 Maciej W. Rozycki
* Copyright (C) 2006 Ralf Baechle (ralf@linux-mips.org)
* Copyright (C) 2012, 2013 MIPS Technologies, Inc. All rights reserved.
*/
enum fields {
RS = 0x001,
RT = 0x002,
RD = 0x004,
RE = 0x008,
SIMM = 0x010,
UIMM = 0x020,
BIMM = 0x040,
JIMM = 0x080,
FUNC = 0x100,
SET = 0x200,
SCIMM = 0x400,
SIMM9 = 0x800,
};
#define OP_MASK 0x3f
#define OP_SH 26
#define RD_MASK 0x1f
#define RD_SH 11
#define RE_MASK 0x1f
#define RE_SH 6
#define IMM_MASK 0xffff
#define IMM_SH 0
#define JIMM_MASK 0x3ffffff
#define JIMM_SH 0
#define FUNC_MASK 0x3f
#define FUNC_SH 0
#define SET_MASK 0x7
#define SET_SH 0
#define SIMM9_SH 7
#define SIMM9_MASK 0x1ff
enum opcode {
insn_addiu, insn_addu, insn_and, insn_andi, insn_bbit0, insn_bbit1,
insn_beq, insn_beql, insn_bgez, insn_bgezl, insn_bgtz, insn_blez,
insn_bltz, insn_bltzl, insn_bne, insn_break, insn_cache, insn_cfc1,
insn_cfcmsa, insn_ctc1, insn_ctcmsa, insn_daddiu, insn_daddu, insn_ddivu,
insn_ddivu_r6, insn_di, insn_dins, insn_dinsm, insn_dinsu, insn_divu,
insn_divu_r6, insn_dmfc0, insn_dmodu, insn_dmtc0, insn_dmultu,
insn_dmulu, insn_drotr, insn_drotr32, insn_dsbh, insn_dshd, insn_dsll,
insn_dsll32, insn_dsllv, insn_dsra, insn_dsra32, insn_dsrav, insn_dsrl,
insn_dsrl32, insn_dsrlv, insn_dsubu, insn_eret, insn_ext, insn_ins,
insn_j, insn_jal, insn_jalr, insn_jr, insn_lb, insn_lbu, insn_ld,
insn_lddir, insn_ldpte, insn_ldx, insn_lh, insn_lhu, insn_ll, insn_lld,
insn_lui, insn_lw, insn_lwu, insn_lwx, insn_mfc0, insn_mfhc0, insn_mfhi,
insn_mflo, insn_modu, insn_movn, insn_movz, insn_mtc0, insn_mthc0,
insn_mthi, insn_mtlo, insn_mul, insn_multu, insn_mulu, insn_muhu, insn_nor,
insn_or, insn_ori, insn_pref, insn_rfe, insn_rotr, insn_sb, insn_sc,
insn_scd, insn_seleqz, insn_selnez, insn_sd, insn_sh, insn_sll,
insn_sllv, insn_slt, insn_slti, insn_sltiu, insn_sltu, insn_sra,
insn_srav, insn_srl, insn_srlv, insn_subu, insn_sw, insn_sync,
insn_syscall, insn_tlbp, insn_tlbr, insn_tlbwi, insn_tlbwr, insn_wait,
insn_wsbh, insn_xor, insn_xori, insn_yield,
insn_invalid /* insn_invalid must be last */
};
struct insn {
u32 match;
enum fields fields;
};
static inline u32 build_rs(u32 arg)
{
WARN(arg & ~RS_MASK, KERN_WARNING "Micro-assembler field overflow\n");
return (arg & RS_MASK) << RS_SH;
}
static inline u32 build_rt(u32 arg)
{
WARN(arg & ~RT_MASK, KERN_WARNING "Micro-assembler field overflow\n");
return (arg & RT_MASK) << RT_SH;
}
static inline u32 build_rd(u32 arg)
{
WARN(arg & ~RD_MASK, KERN_WARNING "Micro-assembler field overflow\n");
return (arg & RD_MASK) << RD_SH;
}
static inline u32 build_re(u32 arg)
{
WARN(arg & ~RE_MASK, KERN_WARNING "Micro-assembler field overflow\n");
return (arg & RE_MASK) << RE_SH;
}
static inline u32 build_simm(s32 arg)
{
WARN(arg > 0x7fff || arg < -0x8000,
KERN_WARNING "Micro-assembler field overflow\n");
return arg & 0xffff;
}
static inline u32 build_uimm(u32 arg)
{
WARN(arg & ~IMM_MASK, KERN_WARNING "Micro-assembler field overflow\n");
return arg & IMM_MASK;
}
static inline u32 build_scimm(u32 arg)
{
WARN(arg & ~SCIMM_MASK,
KERN_WARNING "Micro-assembler field overflow\n");
return (arg & SCIMM_MASK) << SCIMM_SH;
}
static inline u32 build_scimm9(s32 arg)
{
WARN((arg > 0xff || arg < -0x100),
KERN_WARNING "Micro-assembler field overflow\n");
return (arg & SIMM9_MASK) << SIMM9_SH;
}
static inline u32 build_func(u32 arg)
{
WARN(arg & ~FUNC_MASK, KERN_WARNING "Micro-assembler field overflow\n");
return arg & FUNC_MASK;
}
static inline u32 build_set(u32 arg)
{
WARN(arg & ~SET_MASK, KERN_WARNING "Micro-assembler field overflow\n");
return arg & SET_MASK;
}
static void build_insn(u32 **buf, enum opcode opc, ...);
#define I_u1u2u3(op) \
Ip_u1u2u3(op) \
{ \
build_insn(buf, insn##op, a, b, c); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_s3s1s2(op) \
Ip_s3s1s2(op) \
{ \
build_insn(buf, insn##op, b, c, a); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2u1u3(op) \
Ip_u2u1u3(op) \
{ \
build_insn(buf, insn##op, b, a, c); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u3u2u1(op) \
Ip_u3u2u1(op) \
{ \
build_insn(buf, insn##op, c, b, a); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u3u1u2(op) \
Ip_u3u1u2(op) \
{ \
build_insn(buf, insn##op, b, c, a); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u1u2s3(op) \
Ip_u1u2s3(op) \
{ \
build_insn(buf, insn##op, a, b, c); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2s3u1(op) \
Ip_u2s3u1(op) \
{ \
build_insn(buf, insn##op, c, a, b); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2u1s3(op) \
Ip_u2u1s3(op) \
{ \
build_insn(buf, insn##op, b, a, c); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2u1msbu3(op) \
Ip_u2u1msbu3(op) \
{ \
build_insn(buf, insn##op, b, a, c+d-1, c); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2u1msb32u3(op) \
Ip_u2u1msbu3(op) \
{ \
build_insn(buf, insn##op, b, a, c+d-33, c); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2u1msb32msb3(op) \
Ip_u2u1msbu3(op) \
{ \
build_insn(buf, insn##op, b, a, c+d-33, c-32); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2u1msbdu3(op) \
Ip_u2u1msbu3(op) \
{ \
build_insn(buf, insn##op, b, a, d-1, c); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u1u2(op) \
Ip_u1u2(op) \
{ \
build_insn(buf, insn##op, a, b); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u2u1(op) \
Ip_u1u2(op) \
{ \
build_insn(buf, insn##op, b, a); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u1s2(op) \
Ip_u1s2(op) \
{ \
build_insn(buf, insn##op, a, b); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_u1(op) \
Ip_u1(op) \
{ \
build_insn(buf, insn##op, a); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
#define I_0(op) \
Ip_0(op) \
{ \
build_insn(buf, insn##op); \
} \
UASM_EXPORT_SYMBOL(uasm_i##op);
I_u2u1s3(_addiu)
I_u3u1u2(_addu)
I_u2u1u3(_andi)
I_u3u1u2(_and)
I_u1u2s3(_beq)
I_u1u2s3(_beql)
I_u1s2(_bgez)
I_u1s2(_bgezl)
I_u1s2(_bgtz)
I_u1s2(_blez)
I_u1s2(_bltz)
I_u1s2(_bltzl)
I_u1u2s3(_bne)
I_u1(_break)
I_u2s3u1(_cache)
I_u1u2(_cfc1)
I_u2u1(_cfcmsa)
I_u1u2(_ctc1)
I_u2u1(_ctcmsa)
I_u1u2(_ddivu)
I_u3u1u2(_ddivu_r6)
I_u1u2u3(_dmfc0)
I_u3u1u2(_dmodu)
I_u1u2u3(_dmtc0)
I_u1u2(_dmultu)
I_u3u1u2(_dmulu)
I_u2u1s3(_daddiu)
I_u3u1u2(_daddu)
I_u1(_di);
I_u1u2(_divu)
I_u3u1u2(_divu_r6)
I_u2u1(_dsbh);
I_u2u1(_dshd);
I_u2u1u3(_dsll)
I_u2u1u3(_dsll32)
I_u3u2u1(_dsllv)
I_u2u1u3(_dsra)
I_u2u1u3(_dsra32)
I_u3u2u1(_dsrav)
I_u2u1u3(_dsrl)
I_u2u1u3(_dsrl32)
I_u3u2u1(_dsrlv)
I_u2u1u3(_drotr)
I_u2u1u3(_drotr32)
I_u3u1u2(_dsubu)
I_0(_eret)
I_u2u1msbdu3(_ext)
I_u2u1msbu3(_ins)
I_u1(_j)
I_u1(_jal)
I_u2u1(_jalr)
I_u1(_jr)
I_u2s3u1(_lb)
I_u2s3u1(_lbu)
I_u2s3u1(_ld)
I_u2s3u1(_lh)
I_u2s3u1(_lhu)
I_u2s3u1(_ll)
I_u2s3u1(_lld)
I_u1s2(_lui)
I_u2s3u1(_lw)
I_u2s3u1(_lwu)
I_u1u2u3(_mfc0)
I_u1u2u3(_mfhc0)
I_u3u1u2(_modu)
I_u3u1u2(_movn)
I_u3u1u2(_movz)
I_u1(_mfhi)
I_u1(_mflo)
I_u1u2u3(_mtc0)
I_u1u2u3(_mthc0)
I_u1(_mthi)
I_u1(_mtlo)
I_u3u1u2(_mul)
I_u1u2(_multu)
I_u3u1u2(_mulu)
I_u3u1u2(_muhu)
I_u3u1u2(_nor)
I_u3u1u2(_or)
I_u2u1u3(_ori)
I_0(_rfe)
I_u2s3u1(_sb)
I_u2s3u1(_sc)
I_u2s3u1(_scd)
I_u2s3u1(_sd)
I_u3u1u2(_seleqz)
I_u3u1u2(_selnez)
I_u2s3u1(_sh)
I_u2u1u3(_sll)
I_u3u2u1(_sllv)
I_s3s1s2(_slt)
I_u2u1s3(_slti)
I_u2u1s3(_sltiu)
I_u3u1u2(_sltu)
I_u2u1u3(_sra)
I_u3u2u1(_srav)
I_u2u1u3(_srl)
I_u3u2u1(_srlv)
I_u2u1u3(_rotr)
I_u3u1u2(_subu)
I_u2s3u1(_sw)
I_u1(_sync)
I_0(_tlbp)
I_0(_tlbr)
I_0(_tlbwi)
I_0(_tlbwr)
I_u1(_wait);
I_u2u1(_wsbh)
I_u3u1u2(_xor)
I_u2u1u3(_xori)
I_u2u1(_yield)
I_u2u1msbu3(_dins);
I_u2u1msb32u3(_dinsm);
I_u2u1msb32msb3(_dinsu);
I_u1(_syscall);
I_u1u2s3(_bbit0);
I_u1u2s3(_bbit1);
I_u3u1u2(_lwx)
I_u3u1u2(_ldx)
I_u1u2(_ldpte)
I_u2u1u3(_lddir)
#ifdef CONFIG_CPU_CAVIUM_OCTEON
#include <asm/octeon/octeon.h>
void uasm_i_pref(u32 **buf, unsigned int a, signed int b,
unsigned int c)
{
if (OCTEON_IS_MODEL(OCTEON_CN6XXX) && a <= 24 && a != 5)
/*
* As per erratum Core-14449, replace prefetches 0-4,
* 6-24 with 'pref 28'.
*/
build_insn(buf, insn_pref, c, 28, b);
else
build_insn(buf, insn_pref, c, a, b);
}
UASM_EXPORT_SYMBOL(uasm_i_pref);
#else
I_u2s3u1(_pref)
#endif
/* Handle labels. */
void uasm_build_label(struct uasm_label **lab, u32 *addr, int lid)
{
(*lab)->addr = addr;
(*lab)->lab = lid;
(*lab)++;
}
UASM_EXPORT_SYMBOL(uasm_build_label);
int uasm_in_compat_space_p(long addr)
{
/* Is this address in 32bit compat space? */
return addr == (int)addr;
}
UASM_EXPORT_SYMBOL(uasm_in_compat_space_p);
static int uasm_rel_highest(long val)
{
#ifdef CONFIG_64BIT
return ((((val + 0x800080008000L) >> 48) & 0xffff) ^ 0x8000) - 0x8000;
#else
return 0;
#endif
}
static int uasm_rel_higher(long val)
{
#ifdef CONFIG_64BIT
return ((((val + 0x80008000L) >> 32) & 0xffff) ^ 0x8000) - 0x8000;
#else
return 0;
#endif
}
int uasm_rel_hi(long val)
{
return ((((val + 0x8000L) >> 16) & 0xffff) ^ 0x8000) - 0x8000;
}
UASM_EXPORT_SYMBOL(uasm_rel_hi);
int uasm_rel_lo(long val)
{
return ((val & 0xffff) ^ 0x8000) - 0x8000;
}
UASM_EXPORT_SYMBOL(uasm_rel_lo);
void UASM_i_LA_mostly(u32 **buf, unsigned int rs, long addr)
{
if (!uasm_in_compat_space_p(addr)) {
uasm_i_lui(buf, rs, uasm_rel_highest(addr));
if (uasm_rel_higher(addr))
uasm_i_daddiu(buf, rs, rs, uasm_rel_higher(addr));
if (uasm_rel_hi(addr)) {
uasm_i_dsll(buf, rs, rs, 16);
uasm_i_daddiu(buf, rs, rs,
uasm_rel_hi(addr));
uasm_i_dsll(buf, rs, rs, 16);
} else
uasm_i_dsll32(buf, rs, rs, 0);
} else
uasm_i_lui(buf, rs, uasm_rel_hi(addr));
}
UASM_EXPORT_SYMBOL(UASM_i_LA_mostly);
void UASM_i_LA(u32 **buf, unsigned int rs, long addr)
{
UASM_i_LA_mostly(buf, rs, addr);
if (uasm_rel_lo(addr)) {
if (!uasm_in_compat_space_p(addr))
uasm_i_daddiu(buf, rs, rs,
uasm_rel_lo(addr));
else
uasm_i_addiu(buf, rs, rs,
uasm_rel_lo(addr));
}
}
UASM_EXPORT_SYMBOL(UASM_i_LA);
/* Handle relocations. */
void uasm_r_mips_pc16(struct uasm_reloc **rel, u32 *addr, int lid)
{
(*rel)->addr = addr;
(*rel)->type = R_MIPS_PC16;
(*rel)->lab = lid;
(*rel)++;
}
UASM_EXPORT_SYMBOL(uasm_r_mips_pc16);
static inline void __resolve_relocs(struct uasm_reloc *rel,
struct uasm_label *lab);
void uasm_resolve_relocs(struct uasm_reloc *rel,
struct uasm_label *lab)
{
struct uasm_label *l;
for (; rel->lab != UASM_LABEL_INVALID; rel++)
for (l = lab; l->lab != UASM_LABEL_INVALID; l++)
if (rel->lab == l->lab)
__resolve_relocs(rel, l);
}
UASM_EXPORT_SYMBOL(uasm_resolve_relocs);
void uasm_move_relocs(struct uasm_reloc *rel, u32 *first, u32 *end,
long off)
{
for (; rel->lab != UASM_LABEL_INVALID; rel++)
if (rel->addr >= first && rel->addr < end)
rel->addr += off;
}
UASM_EXPORT_SYMBOL(uasm_move_relocs);
void uasm_move_labels(struct uasm_label *lab, u32 *first, u32 *end,
long off)
{
for (; lab->lab != UASM_LABEL_INVALID; lab++)
if (lab->addr >= first && lab->addr < end)
lab->addr += off;
}
UASM_EXPORT_SYMBOL(uasm_move_labels);
void uasm_copy_handler(struct uasm_reloc *rel, struct uasm_label *lab,
u32 *first, u32 *end, u32 *target)
{
long off = (long)(target - first);
memcpy(target, first, (end - first) * sizeof(u32));
uasm_move_relocs(rel, first, end, off);
uasm_move_labels(lab, first, end, off);
}
UASM_EXPORT_SYMBOL(uasm_copy_handler);
int uasm_insn_has_bdelay(struct uasm_reloc *rel, u32 *addr)
{
for (; rel->lab != UASM_LABEL_INVALID; rel++) {
if (rel->addr == addr
&& (rel->type == R_MIPS_PC16
|| rel->type == R_MIPS_26))
return 1;
}
return 0;
}
UASM_EXPORT_SYMBOL(uasm_insn_has_bdelay);
/* Convenience functions for labeled branches. */
void uasm_il_bltz(u32 **p, struct uasm_reloc **r, unsigned int reg,
int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_bltz(p, reg, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_bltz);
void uasm_il_b(u32 **p, struct uasm_reloc **r, int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_b(p, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_b);
void uasm_il_beq(u32 **p, struct uasm_reloc **r, unsigned int r1,
unsigned int r2, int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_beq(p, r1, r2, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_beq);
void uasm_il_beqz(u32 **p, struct uasm_reloc **r, unsigned int reg,
int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_beqz(p, reg, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_beqz);
void uasm_il_beqzl(u32 **p, struct uasm_reloc **r, unsigned int reg,
int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_beqzl(p, reg, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_beqzl);
void uasm_il_bne(u32 **p, struct uasm_reloc **r, unsigned int reg1,
unsigned int reg2, int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_bne(p, reg1, reg2, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_bne);
void uasm_il_bnez(u32 **p, struct uasm_reloc **r, unsigned int reg,
int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_bnez(p, reg, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_bnez);
void uasm_il_bgezl(u32 **p, struct uasm_reloc **r, unsigned int reg,
int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_bgezl(p, reg, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_bgezl);
void uasm_il_bgez(u32 **p, struct uasm_reloc **r, unsigned int reg,
int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_bgez(p, reg, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_bgez);
void uasm_il_bbit0(u32 **p, struct uasm_reloc **r, unsigned int reg,
unsigned int bit, int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_bbit0(p, reg, bit, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_bbit0);
void uasm_il_bbit1(u32 **p, struct uasm_reloc **r, unsigned int reg,
unsigned int bit, int lid)
{
uasm_r_mips_pc16(r, *p, lid);
uasm_i_bbit1(p, reg, bit, 0);
}
UASM_EXPORT_SYMBOL(uasm_il_bbit1);