Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
#
# Makefile for the Linux/SuperH SH-5 backends.
#
obj-y := entry.o probe.o switchto.o
obj-$(CONFIG_SH_FPU) += fpu.o
obj-$(CONFIG_KALLSYMS) += unwind.o
# CPU subtype setup
obj-$(CONFIG_CPU_SH5) += setup-sh5.o
# Primary on-chip clocks (common)
clock-$(CONFIG_CPU_SH5) := clock-sh5.o
obj-y += $(clock-y)

View File

@@ -0,0 +1,79 @@
/*
* arch/sh/kernel/cpu/sh5/clock-sh5.c
*
* SH-5 support for the clock framework
*
* Copyright (C) 2008 Paul Mundt
*
* 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/init.h>
#include <linux/kernel.h>
#include <asm/clock.h>
#include <asm/io.h>
static int ifc_table[] = { 2, 4, 6, 8, 10, 12, 16, 24 };
/* Clock, Power and Reset Controller */
#define CPRC_BLOCK_OFF 0x01010000
#define CPRC_BASE (PHYS_PERIPHERAL_BLOCK + CPRC_BLOCK_OFF)
static unsigned long cprc_base;
static void master_clk_init(struct clk *clk)
{
int idx = (__raw_readl(cprc_base + 0x00) >> 6) & 0x0007;
clk->rate *= ifc_table[idx];
}
static struct sh_clk_ops sh5_master_clk_ops = {
.init = master_clk_init,
};
static unsigned long module_clk_recalc(struct clk *clk)
{
int idx = (__raw_readw(cprc_base) >> 12) & 0x0007;
return clk->parent->rate / ifc_table[idx];
}
static struct sh_clk_ops sh5_module_clk_ops = {
.recalc = module_clk_recalc,
};
static unsigned long bus_clk_recalc(struct clk *clk)
{
int idx = (__raw_readw(cprc_base) >> 3) & 0x0007;
return clk->parent->rate / ifc_table[idx];
}
static struct sh_clk_ops sh5_bus_clk_ops = {
.recalc = bus_clk_recalc,
};
static unsigned long cpu_clk_recalc(struct clk *clk)
{
int idx = (__raw_readw(cprc_base) & 0x0007);
return clk->parent->rate / ifc_table[idx];
}
static struct sh_clk_ops sh5_cpu_clk_ops = {
.recalc = cpu_clk_recalc,
};
static struct sh_clk_ops *sh5_clk_ops[] = {
&sh5_master_clk_ops,
&sh5_module_clk_ops,
&sh5_bus_clk_ops,
&sh5_cpu_clk_ops,
};
void __init arch_init_clk_ops(struct sh_clk_ops **ops, int idx)
{
cprc_base = (unsigned long)ioremap_nocache(CPRC_BASE, 1024);
BUG_ON(!cprc_base);
if (idx < ARRAY_SIZE(sh5_clk_ops))
*ops = sh5_clk_ops[idx];
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
/*
* arch/sh/kernel/cpu/sh5/fpu.c
*
* Copyright (C) 2001 Manuela Cirronis, Paolo Alberelli
* Copyright (C) 2002 STMicroelectronics Limited
* Author : Stuart Menefy
*
* Started from SH4 version:
* Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka
*
* 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/sched.h>
#include <linux/signal.h>
#include <asm/processor.h>
void save_fpu(struct task_struct *tsk)
{
asm volatile("fst.p %0, (0*8), fp0\n\t"
"fst.p %0, (1*8), fp2\n\t"
"fst.p %0, (2*8), fp4\n\t"
"fst.p %0, (3*8), fp6\n\t"
"fst.p %0, (4*8), fp8\n\t"
"fst.p %0, (5*8), fp10\n\t"
"fst.p %0, (6*8), fp12\n\t"
"fst.p %0, (7*8), fp14\n\t"
"fst.p %0, (8*8), fp16\n\t"
"fst.p %0, (9*8), fp18\n\t"
"fst.p %0, (10*8), fp20\n\t"
"fst.p %0, (11*8), fp22\n\t"
"fst.p %0, (12*8), fp24\n\t"
"fst.p %0, (13*8), fp26\n\t"
"fst.p %0, (14*8), fp28\n\t"
"fst.p %0, (15*8), fp30\n\t"
"fst.p %0, (16*8), fp32\n\t"
"fst.p %0, (17*8), fp34\n\t"
"fst.p %0, (18*8), fp36\n\t"
"fst.p %0, (19*8), fp38\n\t"
"fst.p %0, (20*8), fp40\n\t"
"fst.p %0, (21*8), fp42\n\t"
"fst.p %0, (22*8), fp44\n\t"
"fst.p %0, (23*8), fp46\n\t"
"fst.p %0, (24*8), fp48\n\t"
"fst.p %0, (25*8), fp50\n\t"
"fst.p %0, (26*8), fp52\n\t"
"fst.p %0, (27*8), fp54\n\t"
"fst.p %0, (28*8), fp56\n\t"
"fst.p %0, (29*8), fp58\n\t"
"fst.p %0, (30*8), fp60\n\t"
"fst.p %0, (31*8), fp62\n\t"
"fgetscr fr63\n\t"
"fst.s %0, (32*8), fr63\n\t"
: /* no output */
: "r" (&tsk->thread.xstate->hardfpu)
: "memory");
}
void restore_fpu(struct task_struct *tsk)
{
asm volatile("fld.p %0, (0*8), fp0\n\t"
"fld.p %0, (1*8), fp2\n\t"
"fld.p %0, (2*8), fp4\n\t"
"fld.p %0, (3*8), fp6\n\t"
"fld.p %0, (4*8), fp8\n\t"
"fld.p %0, (5*8), fp10\n\t"
"fld.p %0, (6*8), fp12\n\t"
"fld.p %0, (7*8), fp14\n\t"
"fld.p %0, (8*8), fp16\n\t"
"fld.p %0, (9*8), fp18\n\t"
"fld.p %0, (10*8), fp20\n\t"
"fld.p %0, (11*8), fp22\n\t"
"fld.p %0, (12*8), fp24\n\t"
"fld.p %0, (13*8), fp26\n\t"
"fld.p %0, (14*8), fp28\n\t"
"fld.p %0, (15*8), fp30\n\t"
"fld.p %0, (16*8), fp32\n\t"
"fld.p %0, (17*8), fp34\n\t"
"fld.p %0, (18*8), fp36\n\t"
"fld.p %0, (19*8), fp38\n\t"
"fld.p %0, (20*8), fp40\n\t"
"fld.p %0, (21*8), fp42\n\t"
"fld.p %0, (22*8), fp44\n\t"
"fld.p %0, (23*8), fp46\n\t"
"fld.p %0, (24*8), fp48\n\t"
"fld.p %0, (25*8), fp50\n\t"
"fld.p %0, (26*8), fp52\n\t"
"fld.p %0, (27*8), fp54\n\t"
"fld.p %0, (28*8), fp56\n\t"
"fld.p %0, (29*8), fp58\n\t"
"fld.p %0, (30*8), fp60\n\t"
"fld.s %0, (32*8), fr63\n\t"
"fputscr fr63\n\t"
"fld.p %0, (31*8), fp62\n\t"
: /* no output */
: "r" (&tsk->thread.xstate->hardfpu)
: "memory");
}
asmlinkage void do_fpu_error(unsigned long ex, struct pt_regs *regs)
{
struct task_struct *tsk = current;
regs->pc += 4;
force_sig(SIGFPE, tsk);
}

View File

@@ -0,0 +1,75 @@
/*
* arch/sh/kernel/cpu/sh5/probe.c
*
* CPU Subtype Probing for SH-5.
*
* Copyright (C) 2000, 2001 Paolo Alberelli
* Copyright (C) 2003 - 2007 Paul Mundt
*
* 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/init.h>
#include <linux/io.h>
#include <linux/string.h>
#include <asm/processor.h>
#include <asm/cache.h>
#include <asm/tlb.h>
void __cpuinit cpu_probe(void)
{
unsigned long long cir;
/*
* Do peeks in real mode to avoid having to set up a mapping for
* the WPC registers. On SH5-101 cut2, such a mapping would be
* exposed to an address translation erratum which would make it
* hard to set up correctly.
*/
cir = peek_real_address_q(0x0d000008);
if ((cir & 0xffff) == 0x5103)
boot_cpu_data.type = CPU_SH5_103;
else if (((cir >> 32) & 0xffff) == 0x51e2)
/* CPU.VCR aliased at CIR address on SH5-101 */
boot_cpu_data.type = CPU_SH5_101;
boot_cpu_data.family = CPU_FAMILY_SH5;
/*
* First, setup some sane values for the I-cache.
*/
boot_cpu_data.icache.ways = 4;
boot_cpu_data.icache.sets = 256;
boot_cpu_data.icache.linesz = L1_CACHE_BYTES;
boot_cpu_data.icache.way_incr = (1 << 13);
boot_cpu_data.icache.entry_shift = 5;
boot_cpu_data.icache.way_size = boot_cpu_data.icache.sets *
boot_cpu_data.icache.linesz;
boot_cpu_data.icache.entry_mask = 0x1fe0;
boot_cpu_data.icache.flags = 0;
/*
* Next, setup some sane values for the D-cache.
*
* On the SH5, these are pretty consistent with the I-cache settings,
* so we just copy over the existing definitions.. these can be fixed
* up later, especially if we add runtime CPU probing.
*
* Though in the meantime it saves us from having to duplicate all of
* the above definitions..
*/
boot_cpu_data.dcache = boot_cpu_data.icache;
/*
* Setup any cache-related flags here
*/
#if defined(CONFIG_CACHE_WRITETHROUGH)
set_bit(SH_CACHE_MODE_WT, &(boot_cpu_data.dcache.flags));
#elif defined(CONFIG_CACHE_WRITEBACK)
set_bit(SH_CACHE_MODE_WB, &(boot_cpu_data.dcache.flags));
#endif
/* Setup some I/D TLB defaults */
sh64_tlb_init();
}

View File

@@ -0,0 +1,184 @@
/*
* SH5-101/SH5-103 CPU Setup
*
* Copyright (C) 2009 Paul Mundt
*
* 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/platform_device.h>
#include <linux/init.h>
#include <linux/serial.h>
#include <linux/serial_sci.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/sh_timer.h>
#include <asm/addrspace.h>
static struct plat_sci_port scif0_platform_data = {
.mapbase = PHYS_PERIPHERAL_BLOCK + 0x01030000,
.flags = UPF_BOOT_AUTOCONF | UPF_IOREMAP,
.scscr = SCSCR_RE | SCSCR_TE | SCSCR_REIE,
.scbrr_algo_id = SCBRR_ALGO_2,
.type = PORT_SCIF,
.irqs = { 39, 40, 42, 0 },
};
static struct platform_device scif0_device = {
.name = "sh-sci",
.id = 0,
.dev = {
.platform_data = &scif0_platform_data,
},
};
static struct resource rtc_resources[] = {
[0] = {
.start = PHYS_PERIPHERAL_BLOCK + 0x01040000,
.end = PHYS_PERIPHERAL_BLOCK + 0x01040000 + 0x58 - 1,
.flags = IORESOURCE_IO,
},
[1] = {
/* Period IRQ */
.start = IRQ_PRI,
.flags = IORESOURCE_IRQ,
},
[2] = {
/* Carry IRQ */
.start = IRQ_CUI,
.flags = IORESOURCE_IRQ,
},
[3] = {
/* Alarm IRQ */
.start = IRQ_ATI,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device rtc_device = {
.name = "sh-rtc",
.id = -1,
.num_resources = ARRAY_SIZE(rtc_resources),
.resource = rtc_resources,
};
#define TMU_BLOCK_OFF 0x01020000
#define TMU_BASE PHYS_PERIPHERAL_BLOCK + TMU_BLOCK_OFF
#define TMU0_BASE (TMU_BASE + 0x8 + (0xc * 0x0))
#define TMU1_BASE (TMU_BASE + 0x8 + (0xc * 0x1))
#define TMU2_BASE (TMU_BASE + 0x8 + (0xc * 0x2))
static struct sh_timer_config tmu0_platform_data = {
.channel_offset = 0x04,
.timer_bit = 0,
.clockevent_rating = 200,
};
static struct resource tmu0_resources[] = {
[0] = {
.start = TMU0_BASE,
.end = TMU0_BASE + 0xc - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_TUNI0,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device tmu0_device = {
.name = "sh_tmu",
.id = 0,
.dev = {
.platform_data = &tmu0_platform_data,
},
.resource = tmu0_resources,
.num_resources = ARRAY_SIZE(tmu0_resources),
};
static struct sh_timer_config tmu1_platform_data = {
.channel_offset = 0x10,
.timer_bit = 1,
.clocksource_rating = 200,
};
static struct resource tmu1_resources[] = {
[0] = {
.start = TMU1_BASE,
.end = TMU1_BASE + 0xc - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_TUNI1,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device tmu1_device = {
.name = "sh_tmu",
.id = 1,
.dev = {
.platform_data = &tmu1_platform_data,
},
.resource = tmu1_resources,
.num_resources = ARRAY_SIZE(tmu1_resources),
};
static struct sh_timer_config tmu2_platform_data = {
.channel_offset = 0x1c,
.timer_bit = 2,
};
static struct resource tmu2_resources[] = {
[0] = {
.start = TMU2_BASE,
.end = TMU2_BASE + 0xc - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_TUNI2,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device tmu2_device = {
.name = "sh_tmu",
.id = 2,
.dev = {
.platform_data = &tmu2_platform_data,
},
.resource = tmu2_resources,
.num_resources = ARRAY_SIZE(tmu2_resources),
};
static struct platform_device *sh5_early_devices[] __initdata = {
&scif0_device,
&tmu0_device,
&tmu1_device,
&tmu2_device,
};
static struct platform_device *sh5_devices[] __initdata = {
&rtc_device,
};
static int __init sh5_devices_setup(void)
{
int ret;
ret = platform_add_devices(sh5_early_devices,
ARRAY_SIZE(sh5_early_devices));
if (unlikely(ret != 0))
return ret;
return platform_add_devices(sh5_devices,
ARRAY_SIZE(sh5_devices));
}
arch_initcall(sh5_devices_setup);
void __init plat_early_device_setup(void)
{
early_platform_add_devices(sh5_early_devices,
ARRAY_SIZE(sh5_early_devices));
}

View File

@@ -0,0 +1,198 @@
/*
* arch/sh/kernel/cpu/sh5/switchto.S
*
* sh64 context switch
*
* Copyright (C) 2004 Richard Curnow
*
* 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.
*/
.section .text..SHmedia32,"ax"
.little
.balign 32
.type sh64_switch_to,@function
.global sh64_switch_to
.global __sh64_switch_to_end
sh64_switch_to:
/* Incoming args
r2 - prev
r3 - &prev->thread
r4 - next
r5 - &next->thread
Outgoing results
r2 - last (=prev) : this just stays in r2 throughout
Want to create a full (struct pt_regs) on the stack to allow backtracing
functions to work. However, we only need to populate the callee-save
register slots in this structure; since we're a function our ancestors must
have themselves preserved all caller saved state in the stack. This saves
some wasted effort since we won't need to look at the values.
In particular, all caller-save registers are immediately available for
scratch use.
*/
#define FRAME_SIZE (76*8 + 8)
movi FRAME_SIZE, r0
sub.l r15, r0, r15
! Do normal-style register save to support backtrace
st.l r15, 0, r18 ! save link reg
st.l r15, 4, r14 ! save fp
add.l r15, r63, r14 ! setup frame pointer
! hopefully this looks normal to the backtrace now.
addi.l r15, 8, r1 ! base of pt_regs
addi.l r1, 24, r0 ! base of pt_regs.regs
addi.l r0, (63*8), r8 ! base of pt_regs.trregs
/* Note : to be fixed?
struct pt_regs is really designed for holding the state on entry
to an exception, i.e. pc,sr,regs etc. However, for the context
switch state, some of this is not required. But the unwinder takes
struct pt_regs * as an arg so we have to build this structure
to allow unwinding switched tasks in show_state() */
st.q r0, ( 9*8), r9
st.q r0, (10*8), r10
st.q r0, (11*8), r11
st.q r0, (12*8), r12
st.q r0, (13*8), r13
st.q r0, (14*8), r14 ! for unwind, want to look as though we took a trap at
! the point where the process is left in suspended animation, i.e. current
! fp here, not the saved one.
st.q r0, (16*8), r16
st.q r0, (24*8), r24
st.q r0, (25*8), r25
st.q r0, (26*8), r26
st.q r0, (27*8), r27
st.q r0, (28*8), r28
st.q r0, (29*8), r29
st.q r0, (30*8), r30
st.q r0, (31*8), r31
st.q r0, (32*8), r32
st.q r0, (33*8), r33
st.q r0, (34*8), r34
st.q r0, (35*8), r35
st.q r0, (44*8), r44
st.q r0, (45*8), r45
st.q r0, (46*8), r46
st.q r0, (47*8), r47
st.q r0, (48*8), r48
st.q r0, (49*8), r49
st.q r0, (50*8), r50
st.q r0, (51*8), r51
st.q r0, (52*8), r52
st.q r0, (53*8), r53
st.q r0, (54*8), r54
st.q r0, (55*8), r55
st.q r0, (56*8), r56
st.q r0, (57*8), r57
st.q r0, (58*8), r58
st.q r0, (59*8), r59
! do this early as pta->gettr has no pipeline forwarding (=> 5 cycle latency)
! Use a local label to avoid creating a symbol that will confuse the !
! backtrace
pta .Lsave_pc, tr0
gettr tr5, r45
gettr tr6, r46
gettr tr7, r47
st.q r8, (5*8), r45
st.q r8, (6*8), r46
st.q r8, (7*8), r47
! Now switch context
gettr tr0, r9
st.l r3, 0, r15 ! prev->thread.sp
st.l r3, 8, r1 ! prev->thread.kregs
st.l r3, 4, r9 ! prev->thread.pc
st.q r1, 0, r9 ! save prev->thread.pc into pt_regs->pc
! Load PC for next task (init value or save_pc later)
ld.l r5, 4, r18 ! next->thread.pc
! Switch stacks
ld.l r5, 0, r15 ! next->thread.sp
ptabs r18, tr0
! Update current
ld.l r4, 4, r9 ! next->thread_info (2nd element of next task_struct)
putcon r9, kcr0 ! current = next->thread_info
! go to save_pc for a reschedule, or the initial thread.pc for a new process
blink tr0, r63
! Restore (when we come back to a previously saved task)
.Lsave_pc:
addi.l r15, 32, r0 ! r0 = next's regs
addi.l r0, (63*8), r8 ! r8 = next's tr_regs
ld.q r8, (5*8), r45
ld.q r8, (6*8), r46
ld.q r8, (7*8), r47
ptabs r45, tr5
ptabs r46, tr6
ptabs r47, tr7
ld.q r0, ( 9*8), r9
ld.q r0, (10*8), r10
ld.q r0, (11*8), r11
ld.q r0, (12*8), r12
ld.q r0, (13*8), r13
ld.q r0, (14*8), r14
ld.q r0, (16*8), r16
ld.q r0, (24*8), r24
ld.q r0, (25*8), r25
ld.q r0, (26*8), r26
ld.q r0, (27*8), r27
ld.q r0, (28*8), r28
ld.q r0, (29*8), r29
ld.q r0, (30*8), r30
ld.q r0, (31*8), r31
ld.q r0, (32*8), r32
ld.q r0, (33*8), r33
ld.q r0, (34*8), r34
ld.q r0, (35*8), r35
ld.q r0, (44*8), r44
ld.q r0, (45*8), r45
ld.q r0, (46*8), r46
ld.q r0, (47*8), r47
ld.q r0, (48*8), r48
ld.q r0, (49*8), r49
ld.q r0, (50*8), r50
ld.q r0, (51*8), r51
ld.q r0, (52*8), r52
ld.q r0, (53*8), r53
ld.q r0, (54*8), r54
ld.q r0, (55*8), r55
ld.q r0, (56*8), r56
ld.q r0, (57*8), r57
ld.q r0, (58*8), r58
ld.q r0, (59*8), r59
! epilogue
ld.l r15, 0, r18
ld.l r15, 4, r14
ptabs r18, tr0
movi FRAME_SIZE, r0
add r15, r0, r15
blink tr0, r63
__sh64_switch_to_end:
.LFE1:
.size sh64_switch_to,.LFE1-sh64_switch_to

View File

@@ -0,0 +1,345 @@
/*
* arch/sh/kernel/cpu/sh5/unwind.c
*
* Copyright (C) 2004 Paul Mundt
* Copyright (C) 2004 Richard Curnow
*
* 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/kallsyms.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <asm/page.h>
#include <asm/ptrace.h>
#include <asm/processor.h>
#include <asm/io.h>
#include <asm/unwinder.h>
#include <asm/stacktrace.h>
static u8 regcache[63];
/*
* Finding the previous stack frame isn't horribly straightforward as it is
* on some other platforms. In the sh64 case, we don't have "linked" stack
* frames, so we need to do a bit of work to determine the previous frame,
* and in turn, the previous r14/r18 pair.
*
* There are generally a few cases which determine where we can find out
* the r14/r18 values. In the general case, this can be determined by poking
* around the prologue of the symbol PC is in (note that we absolutely must
* have frame pointer support as well as the kernel symbol table mapped,
* otherwise we can't even get this far).
*
* In other cases, such as the interrupt/exception path, we can poke around
* the sp/fp.
*
* Notably, this entire approach is somewhat error prone, and in the event
* that the previous frame cannot be determined, that's all we can do.
* Either way, this still leaves us with a more correct backtrace then what
* we would be able to come up with by walking the stack (which is garbage
* for anything beyond the first frame).
* -- PFM.
*/
static int lookup_prev_stack_frame(unsigned long fp, unsigned long pc,
unsigned long *pprev_fp, unsigned long *pprev_pc,
struct pt_regs *regs)
{
const char *sym;
char namebuf[128];
unsigned long offset;
unsigned long prologue = 0;
unsigned long fp_displacement = 0;
unsigned long fp_prev = 0;
unsigned long offset_r14 = 0, offset_r18 = 0;
int i, found_prologue_end = 0;
sym = kallsyms_lookup(pc, NULL, &offset, NULL, namebuf);
if (!sym)
return -EINVAL;
prologue = pc - offset;
if (!prologue)
return -EINVAL;
/* Validate fp, to avoid risk of dereferencing a bad pointer later.
Assume 128Mb since that's the amount of RAM on a Cayman. Modify
when there is an SH-5 board with more. */
if ((fp < (unsigned long) phys_to_virt(__MEMORY_START)) ||
(fp >= (unsigned long)(phys_to_virt(__MEMORY_START)) + 128*1024*1024) ||
((fp & 7) != 0)) {
return -EINVAL;
}
/*
* Depth to walk, depth is completely arbitrary.
*/
for (i = 0; i < 100; i++, prologue += sizeof(unsigned long)) {
unsigned long op;
u8 major, minor;
u8 src, dest, disp;
op = *(unsigned long *)prologue;
major = (op >> 26) & 0x3f;
src = (op >> 20) & 0x3f;
minor = (op >> 16) & 0xf;
disp = (op >> 10) & 0x3f;
dest = (op >> 4) & 0x3f;
/*
* Stack frame creation happens in a number of ways.. in the
* general case when the stack frame is less than 511 bytes,
* it's generally created by an addi or addi.l:
*
* addi/addi.l r15, -FRAME_SIZE, r15
*
* in the event that the frame size is bigger than this, it's
* typically created using a movi/sub pair as follows:
*
* movi FRAME_SIZE, rX
* sub r15, rX, r15
*/
switch (major) {
case (0x00 >> 2):
switch (minor) {
case 0x8: /* add.l */
case 0x9: /* add */
/* Look for r15, r63, r14 */
if (src == 15 && disp == 63 && dest == 14)
found_prologue_end = 1;
break;
case 0xa: /* sub.l */
case 0xb: /* sub */
if (src != 15 || dest != 15)
continue;
fp_displacement -= regcache[disp];
fp_prev = fp - fp_displacement;
break;
}
break;
case (0xa8 >> 2): /* st.l */
if (src != 15)
continue;
switch (dest) {
case 14:
if (offset_r14 || fp_displacement == 0)
continue;
offset_r14 = (u64)(((((s64)op >> 10) & 0x3ff) << 54) >> 54);
offset_r14 *= sizeof(unsigned long);
offset_r14 += fp_displacement;
break;
case 18:
if (offset_r18 || fp_displacement == 0)
continue;
offset_r18 = (u64)(((((s64)op >> 10) & 0x3ff) << 54) >> 54);
offset_r18 *= sizeof(unsigned long);
offset_r18 += fp_displacement;
break;
}
break;
case (0xcc >> 2): /* movi */
if (dest >= 63) {
printk(KERN_NOTICE "%s: Invalid dest reg %d "
"specified in movi handler. Failed "
"opcode was 0x%lx: ", __func__,
dest, op);
continue;
}
/* Sign extend */
regcache[dest] =
((((s64)(u64)op >> 10) & 0xffff) << 54) >> 54;
break;
case (0xd0 >> 2): /* addi */
case (0xd4 >> 2): /* addi.l */
/* Look for r15, -FRAME_SIZE, r15 */
if (src != 15 || dest != 15)
continue;
/* Sign extended frame size.. */
fp_displacement +=
(u64)(((((s64)op >> 10) & 0x3ff) << 54) >> 54);
fp_prev = fp - fp_displacement;
break;
}
if (found_prologue_end && offset_r14 && (offset_r18 || *pprev_pc) && fp_prev)
break;
}
if (offset_r14 == 0 || fp_prev == 0) {
if (!offset_r14)
pr_debug("Unable to find r14 offset\n");
if (!fp_prev)
pr_debug("Unable to find previous fp\n");
return -EINVAL;
}
/* For innermost leaf function, there might not be a offset_r18 */
if (!*pprev_pc && (offset_r18 == 0))
return -EINVAL;
*pprev_fp = *(unsigned long *)(fp_prev + offset_r14);
if (offset_r18)
*pprev_pc = *(unsigned long *)(fp_prev + offset_r18);
*pprev_pc &= ~1;
return 0;
}
/*
* Don't put this on the stack since we'll want to call in to
* sh64_unwinder_dump() when we're close to underflowing the stack
* anyway.
*/
static struct pt_regs here_regs;
extern const char syscall_ret;
extern const char ret_from_syscall;
extern const char ret_from_exception;
extern const char ret_from_irq;
static void sh64_unwind_inner(const struct stacktrace_ops *ops,
void *data, struct pt_regs *regs);
static inline void unwind_nested(const struct stacktrace_ops *ops, void *data,
unsigned long pc, unsigned long fp)
{
if ((fp >= __MEMORY_START) &&
((fp & 7) == 0))
sh64_unwind_inner(ops, data, (struct pt_regs *)fp);
}
static void sh64_unwind_inner(const struct stacktrace_ops *ops,
void *data, struct pt_regs *regs)
{
unsigned long pc, fp;
int ofs = 0;
int first_pass;
pc = regs->pc & ~1;
fp = regs->regs[14];
first_pass = 1;
for (;;) {
int cond;
unsigned long next_fp, next_pc;
if (pc == ((unsigned long)&syscall_ret & ~1)) {
printk("SYSCALL\n");
unwind_nested(ops, data, pc, fp);
return;
}
if (pc == ((unsigned long)&ret_from_syscall & ~1)) {
printk("SYSCALL (PREEMPTED)\n");
unwind_nested(ops, data, pc, fp);
return;
}
/* In this case, the PC is discovered by lookup_prev_stack_frame but
it has 4 taken off it to look like the 'caller' */
if (pc == ((unsigned long)&ret_from_exception & ~1)) {
printk("EXCEPTION\n");
unwind_nested(ops, data, pc, fp);
return;
}
if (pc == ((unsigned long)&ret_from_irq & ~1)) {
printk("IRQ\n");
unwind_nested(ops, data, pc, fp);
return;
}
cond = ((pc >= __MEMORY_START) && (fp >= __MEMORY_START) &&
((pc & 3) == 0) && ((fp & 7) == 0));
pc -= ofs;
ops->address(data, pc, 1);
if (first_pass) {
/* If the innermost frame is a leaf function, it's
* possible that r18 is never saved out to the stack.
*/
next_pc = regs->regs[18];
} else {
next_pc = 0;
}
if (lookup_prev_stack_frame(fp, pc, &next_fp, &next_pc, regs) == 0) {
ofs = sizeof(unsigned long);
pc = next_pc & ~1;
fp = next_fp;
} else {
printk("Unable to lookup previous stack frame\n");
break;
}
first_pass = 0;
}
printk("\n");
}
static void sh64_unwinder_dump(struct task_struct *task,
struct pt_regs *regs,
unsigned long *sp,
const struct stacktrace_ops *ops,
void *data)
{
if (!regs) {
/*
* Fetch current regs if we have no other saved state to back
* trace from.
*/
regs = &here_regs;
__asm__ __volatile__ ("ori r14, 0, %0" : "=r" (regs->regs[14]));
__asm__ __volatile__ ("ori r15, 0, %0" : "=r" (regs->regs[15]));
__asm__ __volatile__ ("ori r18, 0, %0" : "=r" (regs->regs[18]));
__asm__ __volatile__ ("gettr tr0, %0" : "=r" (regs->tregs[0]));
__asm__ __volatile__ ("gettr tr1, %0" : "=r" (regs->tregs[1]));
__asm__ __volatile__ ("gettr tr2, %0" : "=r" (regs->tregs[2]));
__asm__ __volatile__ ("gettr tr3, %0" : "=r" (regs->tregs[3]));
__asm__ __volatile__ ("gettr tr4, %0" : "=r" (regs->tregs[4]));
__asm__ __volatile__ ("gettr tr5, %0" : "=r" (regs->tregs[5]));
__asm__ __volatile__ ("gettr tr6, %0" : "=r" (regs->tregs[6]));
__asm__ __volatile__ ("gettr tr7, %0" : "=r" (regs->tregs[7]));
__asm__ __volatile__ (
"pta 0f, tr0\n\t"
"blink tr0, %0\n\t"
"0: nop"
: "=r" (regs->pc)
);
}
sh64_unwind_inner(ops, data, regs);
}
static struct unwinder sh64_unwinder = {
.name = "sh64-unwinder",
.dump = sh64_unwinder_dump,
.rating = 150,
};
static int __init sh64_unwinder_init(void)
{
return unwinder_register(&sh64_unwinder);
}
early_initcall(sh64_unwinder_init);