linux/arch/sparc/mm/btfixup.c
David S. Miller 5d83d66635 sparc32: Move cache and TLB flushes over to method ops.
This eliminated most of the remaining users of btfixup.

There are some complications because of the special cases we
have for sun4d, leon, and some flavors of viking.

It was found that there are no cases where a flush_page_for_dma
method was not hooked up to something, so the "noflush" iommu
methods were removed.

Add some documentation to the viking_sun4d_smp_ops to describe exactly
the hardware bug which causes us to need special TLB flushing on
sun4d.

Signed-off-by: David S. Miller <davem@davemloft.net>
2012-05-13 20:49:31 -07:00

317 lines
9.6 KiB
C

/* btfixup.c: Boot time code fixup and relocator, so that
* we can get rid of most indirect calls to achieve single
* image sun4c and srmmu kernel.
*
* Copyright (C) 1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz)
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/btfixup.h>
#include <asm/page.h>
#include <asm/pgalloc.h>
#include <asm/pgtable.h>
#include <asm/oplib.h>
#include <asm/cacheflush.h>
#define BTFIXUP_OPTIMIZE_NOP
#define BTFIXUP_OPTIMIZE_OTHER
extern char *srmmu_name;
static char version[] __initdata = "Boot time fixup v1.6. 4/Mar/98 Jakub Jelinek (jj@ultra.linux.cz). Patching kernel for ";
static char str_srmmu[] __initdata = "srmmu[%s]/";
static char str_iommu[] __initdata = "iommu\n";
static char str_iounit[] __initdata = "io-unit\n";
static int visited __initdata = 0;
extern unsigned int ___btfixup_start[], ___btfixup_end[], __init_begin[], __init_end[], __init_text_end[];
extern unsigned int _stext[], _end[], __start___ksymtab[], __stop___ksymtab[];
static char wrong_f[] __initdata = "Trying to set f fixup %p to invalid function %08x\n";
static char wrong_b[] __initdata = "Trying to set b fixup %p to invalid function %08x\n";
static char wrong_s[] __initdata = "Trying to set s fixup %p to invalid value %08x\n";
static char wrong_h[] __initdata = "Trying to set h fixup %p to invalid value %08x\n";
static char wrong_a[] __initdata = "Trying to set a fixup %p to invalid value %08x\n";
static char wrong[] __initdata = "Wrong address for %c fixup %p\n";
static char insn_f[] __initdata = "Fixup f %p refers to weird instructions at %p[%08x,%08x]\n";
static char insn_b[] __initdata = "Fixup b %p doesn't refer to a SETHI at %p[%08x]\n";
static char insn_s[] __initdata = "Fixup s %p doesn't refer to an OR at %p[%08x]\n";
static char insn_h[] __initdata = "Fixup h %p doesn't refer to a SETHI at %p[%08x]\n";
static char insn_a[] __initdata = "Fixup a %p doesn't refer to a SETHI nor OR at %p[%08x]\n";
static char insn_i[] __initdata = "Fixup i %p doesn't refer to a valid instruction at %p[%08x]\n";
static char wrong_setaddr[] __initdata = "Garbled CALL/INT patch at %p[%08x,%08x,%08x]=%08x\n";
#ifdef BTFIXUP_OPTIMIZE_OTHER
static void __init set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value)
{
if (!fmangled)
*addr = value;
else {
unsigned int *q = (unsigned int *)q1;
if (*addr == 0x01000000) {
/* Noped */
*q = value;
} else if (addr[-1] == *q) {
/* Moved */
addr[-1] = value;
*q = value;
} else {
prom_printf(wrong_setaddr, addr-1, addr[-1], *addr, *q, value);
prom_halt();
}
}
}
#else
static inline void set_addr(unsigned int *addr, unsigned int q1, int fmangled, unsigned int value)
{
*addr = value;
}
#endif
void __init btfixup(void)
{
unsigned int *p, *q;
int type, count;
unsigned insn;
unsigned *addr;
int fmangled = 0;
if (!visited) {
visited++;
printk(version);
printk(str_srmmu, srmmu_name);
if (sparc_cpu_model == sun4d)
printk(str_iounit);
else
printk(str_iommu);
}
for (p = ___btfixup_start; p < ___btfixup_end; ) {
count = p[2];
q = p + 3;
switch (type = *(unsigned char *)p) {
case 'f':
count = p[3];
q = p + 4;
if (((p[0] & 1) || p[1])
&& ((p[1] & 3) || (unsigned *)(p[1]) < _stext || (unsigned *)(p[1]) >= _end)) {
prom_printf(wrong_f, p, p[1]);
prom_halt();
}
break;
case 'b':
if (p[1] < (unsigned long)__init_begin || p[1] >= (unsigned long)__init_text_end || (p[1] & 3)) {
prom_printf(wrong_b, p, p[1]);
prom_halt();
}
break;
case 's':
if (p[1] + 0x1000 >= 0x2000) {
prom_printf(wrong_s, p, p[1]);
prom_halt();
}
break;
case 'h':
if (p[1] & 0x3ff) {
prom_printf(wrong_h, p, p[1]);
prom_halt();
}
break;
case 'a':
if (p[1] + 0x1000 >= 0x2000 && (p[1] & 0x3ff)) {
prom_printf(wrong_a, p, p[1]);
prom_halt();
}
break;
}
if (p[0] & 1) {
p[0] &= ~1;
while (count) {
fmangled = 0;
addr = (unsigned *)*q;
if (addr < _stext || addr >= _end) {
prom_printf(wrong, type, p);
prom_halt();
}
insn = *addr;
#ifdef BTFIXUP_OPTIMIZE_OTHER
if (type != 'f' && q[1]) {
insn = *(unsigned int *)q[1];
if (!insn || insn == 1)
insn = *addr;
else
fmangled = 1;
}
#endif
switch (type) {
case 'f': /* CALL */
if (addr >= __start___ksymtab && addr < __stop___ksymtab) {
*addr = p[1];
break;
} else if (!q[1]) {
if ((insn & 0xc1c00000) == 0x01000000) { /* SETHI */
*addr = (insn & 0xffc00000) | (p[1] >> 10); break;
} else if ((insn & 0xc1f82000) == 0x80102000) { /* OR X, %LO(i), Y */
*addr = (insn & 0xffffe000) | (p[1] & 0x3ff); break;
} else if ((insn & 0xc0000000) != 0x40000000) { /* !CALL */
bad_f:
prom_printf(insn_f, p, addr, insn, addr[1]);
prom_halt();
}
} else if (q[1] != 1)
addr[1] = q[1];
if (p[2] == BTFIXUPCALL_NORM) {
norm_f:
*addr = 0x40000000 | ((p[1] - (unsigned)addr) >> 2);
q[1] = 0;
break;
}
#ifndef BTFIXUP_OPTIMIZE_NOP
goto norm_f;
#else
if (!(addr[1] & 0x80000000)) {
if ((addr[1] & 0xc1c00000) != 0x01000000) /* !SETHI */
goto bad_f; /* CALL, Bicc, FBfcc, CBccc are weird in delay slot, aren't they? */
} else {
if ((addr[1] & 0x01800000) == 0x01800000) {
if ((addr[1] & 0x01f80000) == 0x01e80000) {
/* RESTORE */
goto norm_f; /* It is dangerous to patch that */
}
goto bad_f;
}
if ((addr[1] & 0xffffe003) == 0x9e03e000) {
/* ADD %O7, XX, %o7 */
int displac = (addr[1] << 19);
displac = (displac >> 21) + 2;
*addr = (0x10800000) + (displac & 0x3fffff);
q[1] = addr[1];
addr[1] = p[2];
break;
}
if ((addr[1] & 0x201f) == 0x200f || (addr[1] & 0x7c000) == 0x3c000)
goto norm_f; /* Someone is playing bad tricks with us: rs1 or rs2 is o7 */
if ((addr[1] & 0x3e000000) == 0x1e000000)
goto norm_f; /* rd is %o7. We'd better take care. */
}
if (p[2] == BTFIXUPCALL_NOP) {
*addr = 0x01000000;
q[1] = 1;
break;
}
#ifndef BTFIXUP_OPTIMIZE_OTHER
goto norm_f;
#else
if (addr[1] == 0x01000000) { /* NOP in the delay slot */
q[1] = addr[1];
*addr = p[2];
break;
}
if ((addr[1] & 0xc0000000) != 0xc0000000) {
/* Not a memory operation */
if ((addr[1] & 0x30000000) == 0x10000000) {
/* Ok, non-memory op with rd %oX */
if ((addr[1] & 0x3e000000) == 0x1c000000)
goto bad_f; /* Aiee. Someone is playing strange %sp tricks */
if ((addr[1] & 0x3e000000) > 0x12000000 ||
((addr[1] & 0x3e000000) == 0x12000000 &&
p[2] != BTFIXUPCALL_STO1O0 && p[2] != BTFIXUPCALL_SWAPO0O1) ||
((p[2] & 0xffffe000) == BTFIXUPCALL_RETINT(0))) {
/* Nobody uses the result. We can nop it out. */
*addr = p[2];
q[1] = addr[1];
addr[1] = 0x01000000;
break;
}
if ((addr[1] & 0xf1ffffe0) == 0x90100000) {
/* MOV %reg, %Ox */
if ((addr[1] & 0x3e000000) == 0x10000000 &&
(p[2] & 0x7c000) == 0x20000) {
/* Ok, it is call xx; mov reg, %o0 and call optimizes
to doing something on %o0. Patch the patch. */
*addr = (p[2] & ~0x7c000) | ((addr[1] & 0x1f) << 14);
q[1] = addr[1];
addr[1] = 0x01000000;
break;
}
if ((addr[1] & 0x3e000000) == 0x12000000 &&
p[2] == BTFIXUPCALL_STO1O0) {
*addr = (p[2] & ~0x3e000000) | ((addr[1] & 0x1f) << 25);
q[1] = addr[1];
addr[1] = 0x01000000;
break;
}
}
}
}
*addr = addr[1];
q[1] = addr[1];
addr[1] = p[2];
break;
#endif /* BTFIXUP_OPTIMIZE_OTHER */
#endif /* BTFIXUP_OPTIMIZE_NOP */
case 'b': /* BLACKBOX */
/* Has to be sethi i, xx */
if ((insn & 0xc1c00000) != 0x01000000) {
prom_printf(insn_b, p, addr, insn);
prom_halt();
} else {
void (*do_fixup)(unsigned *);
do_fixup = (void (*)(unsigned *))p[1];
do_fixup(addr);
}
break;
case 's': /* SIMM13 */
/* Has to be or %g0, i, xx */
if ((insn & 0xc1ffe000) != 0x80102000) {
prom_printf(insn_s, p, addr, insn);
prom_halt();
}
set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x1fff));
break;
case 'h': /* SETHI */
/* Has to be sethi i, xx */
if ((insn & 0xc1c00000) != 0x01000000) {
prom_printf(insn_h, p, addr, insn);
prom_halt();
}
set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10));
break;
case 'a': /* HALF */
/* Has to be sethi i, xx or or %g0, i, xx */
if ((insn & 0xc1c00000) != 0x01000000 &&
(insn & 0xc1ffe000) != 0x80102000) {
prom_printf(insn_a, p, addr, insn);
prom_halt();
}
if (p[1] & 0x3ff)
set_addr(addr, q[1], fmangled,
(insn & 0x3e000000) | 0x80102000 | (p[1] & 0x1fff));
else
set_addr(addr, q[1], fmangled,
(insn & 0x3e000000) | 0x01000000 | (p[1] >> 10));
break;
case 'i': /* INT */
if ((insn & 0xc1c00000) == 0x01000000) /* %HI */
set_addr(addr, q[1], fmangled, (insn & 0xffc00000) | (p[1] >> 10));
else if ((insn & 0x80002000) == 0x80002000) /* %LO */
set_addr(addr, q[1], fmangled, (insn & 0xffffe000) | (p[1] & 0x3ff));
else {
prom_printf(insn_i, p, addr, insn);
prom_halt();
}
break;
}
count -= 2;
q += 2;
}
} else
p = q + count;
}
#ifdef CONFIG_SMP
local_ops->cache_all();
#else
sparc32_cachetlb_ops->cache_all();
#endif
}