diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/sh64/kernel |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'arch/sh64/kernel')
30 files changed, 10264 insertions, 0 deletions
diff --git a/arch/sh64/kernel/Makefile b/arch/sh64/kernel/Makefile new file mode 100644 index 00000000000..5816657c079 --- /dev/null +++ b/arch/sh64/kernel/Makefile @@ -0,0 +1,36 @@ +# +# 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) 2000, 2001 Paolo Alberelli +# Copyright (C) 2003 Paul Mundt +# +# Makefile for the Linux sh64 kernel. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# + +extra-y := head.o init_task.o vmlinux.lds + +obj-y := process.o signal.o entry.o traps.o irq.o irq_intc.o \ + ptrace.o setup.o time.o sys_sh64.o semaphore.o sh_ksyms.o \ + switchto.o syscalls.o + +obj-$(CONFIG_HEARTBEAT) += led.o +obj-$(CONFIG_SH_ALPHANUMERIC) += alphanum.o +obj-$(CONFIG_SH_DMA) += dma.o +obj-$(CONFIG_SH_FPU) += fpu.o +obj-$(CONFIG_EARLY_PRINTK) += early_printk.o +obj-$(CONFIG_KALLSYMS) += unwind.o +obj-$(CONFIG_PCI) += pci-dma.o pcibios.o +obj-$(CONFIG_MODULES) += module.o + +ifeq ($(CONFIG_PCI),y) +obj-$(CONFIG_CPU_SH5) += pci_sh5.o +endif + +USE_STANDARD_AS_RULE := true + diff --git a/arch/sh64/kernel/alphanum.c b/arch/sh64/kernel/alphanum.c new file mode 100644 index 00000000000..56d6f9f7152 --- /dev/null +++ b/arch/sh64/kernel/alphanum.c @@ -0,0 +1,45 @@ +/* + * arch/sh64/kernel/alpanum.c + * + * Copyright (C) 2002 Stuart Menefy <stuart.menefy@st.com> + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Machine-independent functions for handling 8-digit alphanumeric display + * (e.g. Agilent HDSP-253x) + */ +#include <linux/config.h> +#include <linux/stddef.h> +#include <linux/sched.h> + +void mach_alphanum(int pos, unsigned char val); +void mach_led(int pos, int val); + +void print_seg(char *file, int line) +{ + int i; + unsigned int nibble; + + for (i = 0; i < 5; i++) { + mach_alphanum(i, file[i]); + } + + for (i = 0; i < 3; i++) { + nibble = ((line >> (i * 4)) & 0xf); + mach_alphanum(7 - i, nibble + ((nibble > 9) ? 55 : 48)); + } +} + +void print_seg_num(unsigned num) +{ + int i; + unsigned int nibble; + + for (i = 0; i < 8; i++) { + nibble = ((num >> (i * 4)) & 0xf); + + mach_alphanum(7 - i, nibble + ((nibble > 9) ? 55 : 48)); + } +} + diff --git a/arch/sh64/kernel/asm-offsets.c b/arch/sh64/kernel/asm-offsets.c new file mode 100644 index 00000000000..ca76537c16c --- /dev/null +++ b/arch/sh64/kernel/asm-offsets.c @@ -0,0 +1,33 @@ +/* + * This program is used to generate definitions needed by + * assembly language modules. + * + * We use the technique used in the OSF Mach kernel code: + * generate asm statements containing #defines, + * compile this file to assembler, and then extract the + * #defines from the assembly-language output. + */ + +#include <linux/stddef.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <asm/thread_info.h> + +#define DEFINE(sym, val) \ + asm volatile("\n->" #sym " %0 " #val : : "i" (val)) + +#define BLANK() asm volatile("\n->" : : ) + +int main(void) +{ + /* offsets into the thread_info struct */ + DEFINE(TI_TASK, offsetof(struct thread_info, task)); + DEFINE(TI_EXEC_DOMAIN, offsetof(struct thread_info, exec_domain)); + DEFINE(TI_FLAGS, offsetof(struct thread_info, flags)); + DEFINE(TI_PRE_COUNT, offsetof(struct thread_info, preempt_count)); + DEFINE(TI_CPU, offsetof(struct thread_info, cpu)); + DEFINE(TI_ADDR_LIMIT, offsetof(struct thread_info, addr_limit)); + DEFINE(TI_RESTART_BLOCK,offsetof(struct thread_info, restart_block)); + + return 0; +} diff --git a/arch/sh64/kernel/dma.c b/arch/sh64/kernel/dma.c new file mode 100644 index 00000000000..09cd9f4670b --- /dev/null +++ b/arch/sh64/kernel/dma.c @@ -0,0 +1,297 @@ +/* + * arch/sh64/kernel/dma.c + * + * DMA routines for the SH-5 DMAC. + * + * Copyright (C) 2003 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/module.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/irq.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <asm/hardware.h> +#include <asm/dma.h> +#include <asm/signal.h> +#include <asm/errno.h> +#include <asm/io.h> + +typedef struct { + unsigned long dev_addr; + unsigned long mem_addr; + + unsigned int mode; + unsigned int count; +} dma_info_t; + +static dma_info_t dma_info[MAX_DMA_CHANNELS]; +static DEFINE_SPINLOCK(dma_spin_lock); + +/* arch/sh64/kernel/irq_intc.c */ +extern void make_intc_irq(unsigned int irq); + +/* DMAC Interrupts */ +#define DMA_IRQ_DMTE0 18 +#define DMA_IRQ_DERR 22 + +#define DMAC_COMMON_BASE (dmac_base + 0x08) +#define DMAC_SAR_BASE (dmac_base + 0x10) +#define DMAC_DAR_BASE (dmac_base + 0x18) +#define DMAC_COUNT_BASE (dmac_base + 0x20) +#define DMAC_CTRL_BASE (dmac_base + 0x28) +#define DMAC_STATUS_BASE (dmac_base + 0x30) + +#define DMAC_SAR(n) (DMAC_SAR_BASE + ((n) * 0x28)) +#define DMAC_DAR(n) (DMAC_DAR_BASE + ((n) * 0x28)) +#define DMAC_COUNT(n) (DMAC_COUNT_BASE + ((n) * 0x28)) +#define DMAC_CTRL(n) (DMAC_CTRL_BASE + ((n) * 0x28)) +#define DMAC_STATUS(n) (DMAC_STATUS_BASE + ((n) * 0x28)) + +/* DMAC.COMMON Bit Definitions */ +#define DMAC_COMMON_PR 0x00000001 /* Priority */ + /* Bits 1-2 Reserved */ +#define DMAC_COMMON_ME 0x00000008 /* Master Enable */ +#define DMAC_COMMON_NMI 0x00000010 /* NMI Flag */ + /* Bits 5-6 Reserved */ +#define DMAC_COMMON_ER 0x00000780 /* Error Response */ +#define DMAC_COMMON_AAE 0x00007800 /* Address Alignment Error */ + /* Bits 15-63 Reserved */ + +/* DMAC.SAR Bit Definitions */ +#define DMAC_SAR_ADDR 0xffffffff /* Source Address */ + +/* DMAC.DAR Bit Definitions */ +#define DMAC_DAR_ADDR 0xffffffff /* Destination Address */ + +/* DMAC.COUNT Bit Definitions */ +#define DMAC_COUNT_CNT 0xffffffff /* Transfer Count */ + +/* DMAC.CTRL Bit Definitions */ +#define DMAC_CTRL_TS 0x00000007 /* Transfer Size */ +#define DMAC_CTRL_SI 0x00000018 /* Source Increment */ +#define DMAC_CTRL_DI 0x00000060 /* Destination Increment */ +#define DMAC_CTRL_RS 0x00000780 /* Resource Select */ +#define DMAC_CTRL_IE 0x00000800 /* Interrupt Enable */ +#define DMAC_CTRL_TE 0x00001000 /* Transfer Enable */ + /* Bits 15-63 Reserved */ + +/* DMAC.STATUS Bit Definitions */ +#define DMAC_STATUS_TE 0x00000001 /* Transfer End */ +#define DMAC_STATUS_AAE 0x00000002 /* Address Alignment Error */ + /* Bits 2-63 Reserved */ + +static unsigned long dmac_base; + +void set_dma_count(unsigned int chan, unsigned int count); +void set_dma_addr(unsigned int chan, unsigned int addr); + +static irqreturn_t dma_mte(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned int chan = irq - DMA_IRQ_DMTE0; + dma_info_t *info = dma_info + chan; + u64 status; + + if (info->mode & DMA_MODE_WRITE) { + sh64_out64(info->mem_addr & DMAC_SAR_ADDR, DMAC_SAR(chan)); + } else { + sh64_out64(info->mem_addr & DMAC_DAR_ADDR, DMAC_DAR(chan)); + } + + set_dma_count(chan, info->count); + + /* Clear the TE bit */ + status = sh64_in64(DMAC_STATUS(chan)); + status &= ~DMAC_STATUS_TE; + sh64_out64(status, DMAC_STATUS(chan)); + + return IRQ_HANDLED; +} + +static struct irqaction irq_dmte = { + .handler = dma_mte, + .flags = SA_INTERRUPT, + .name = "DMA MTE", +}; + +static irqreturn_t dma_err(int irq, void *dev_id, struct pt_regs *regs) +{ + u64 tmp; + u8 chan; + + printk(KERN_NOTICE "DMAC: Got a DMA Error!\n"); + + tmp = sh64_in64(DMAC_COMMON_BASE); + + /* Check for the type of error */ + if ((chan = tmp & DMAC_COMMON_AAE)) { + /* It's an address alignment error.. */ + printk(KERN_NOTICE "DMAC: Alignment error on channel %d, ", chan); + + printk(KERN_NOTICE "SAR: 0x%08llx, DAR: 0x%08llx, COUNT: %lld\n", + (sh64_in64(DMAC_SAR(chan)) & DMAC_SAR_ADDR), + (sh64_in64(DMAC_DAR(chan)) & DMAC_DAR_ADDR), + (sh64_in64(DMAC_COUNT(chan)) & DMAC_COUNT_CNT)); + + } else if ((chan = tmp & DMAC_COMMON_ER)) { + /* Something else went wrong.. */ + printk(KERN_NOTICE "DMAC: Error on channel %d\n", chan); + } + + /* Reset the ME bit to clear the interrupt */ + tmp |= DMAC_COMMON_ME; + sh64_out64(tmp, DMAC_COMMON_BASE); + + return IRQ_HANDLED; +} + +static struct irqaction irq_derr = { + .handler = dma_err, + .flags = SA_INTERRUPT, + .name = "DMA Error", +}; + +static inline unsigned long calc_xmit_shift(unsigned int chan) +{ + return sh64_in64(DMAC_CTRL(chan)) & 0x03; +} + +void setup_dma(unsigned int chan, dma_info_t *info) +{ + unsigned int irq = DMA_IRQ_DMTE0 + chan; + dma_info_t *dma = dma_info + chan; + + make_intc_irq(irq); + setup_irq(irq, &irq_dmte); + dma = info; +} + +void enable_dma(unsigned int chan) +{ + u64 ctrl; + + ctrl = sh64_in64(DMAC_CTRL(chan)); + ctrl |= DMAC_CTRL_TE; + sh64_out64(ctrl, DMAC_CTRL(chan)); +} + +void disable_dma(unsigned int chan) +{ + u64 ctrl; + + ctrl = sh64_in64(DMAC_CTRL(chan)); + ctrl &= ~DMAC_CTRL_TE; + sh64_out64(ctrl, DMAC_CTRL(chan)); +} + +void set_dma_mode(unsigned int chan, char mode) +{ + dma_info_t *info = dma_info + chan; + + info->mode = mode; + + set_dma_addr(chan, info->mem_addr); + set_dma_count(chan, info->count); +} + +void set_dma_addr(unsigned int chan, unsigned int addr) +{ + dma_info_t *info = dma_info + chan; + unsigned long sar, dar; + + info->mem_addr = addr; + sar = (info->mode & DMA_MODE_WRITE) ? info->mem_addr : info->dev_addr; + dar = (info->mode & DMA_MODE_WRITE) ? info->dev_addr : info->mem_addr; + + sh64_out64(sar & DMAC_SAR_ADDR, DMAC_SAR(chan)); + sh64_out64(dar & DMAC_SAR_ADDR, DMAC_DAR(chan)); +} + +void set_dma_count(unsigned int chan, unsigned int count) +{ + dma_info_t *info = dma_info + chan; + u64 tmp; + + info->count = count; + + tmp = (info->count >> calc_xmit_shift(chan)) & DMAC_COUNT_CNT; + + sh64_out64(tmp, DMAC_COUNT(chan)); +} + +unsigned long claim_dma_lock(void) +{ + unsigned long flags; + + spin_lock_irqsave(&dma_spin_lock, flags); + + return flags; +} + +void release_dma_lock(unsigned long flags) +{ + spin_unlock_irqrestore(&dma_spin_lock, flags); +} + +int get_dma_residue(unsigned int chan) +{ + return sh64_in64(DMAC_COUNT(chan) << calc_xmit_shift(chan)); +} + +int __init init_dma(void) +{ + struct vcr_info vcr; + u64 tmp; + + /* Remap the DMAC */ + dmac_base = onchip_remap(PHYS_DMAC_BLOCK, 1024, "DMAC"); + if (!dmac_base) { + printk(KERN_ERR "Unable to remap DMAC\n"); + return -ENOMEM; + } + + /* Report DMAC.VCR Info */ + vcr = sh64_get_vcr_info(dmac_base); + printk("DMAC: Module ID: 0x%04x, Module version: 0x%04x\n", + vcr.mod_id, vcr.mod_vers); + + /* Set the ME bit */ + tmp = sh64_in64(DMAC_COMMON_BASE); + tmp |= DMAC_COMMON_ME; + sh64_out64(tmp, DMAC_COMMON_BASE); + + /* Enable the DMAC Error Interrupt */ + make_intc_irq(DMA_IRQ_DERR); + setup_irq(DMA_IRQ_DERR, &irq_derr); + + return 0; +} + +static void __exit exit_dma(void) +{ + onchip_unmap(dmac_base); + free_irq(DMA_IRQ_DERR, 0); +} + +module_init(init_dma); +module_exit(exit_dma); + +MODULE_AUTHOR("Paul Mundt"); +MODULE_DESCRIPTION("DMA API for SH-5 DMAC"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(setup_dma); +EXPORT_SYMBOL(claim_dma_lock); +EXPORT_SYMBOL(release_dma_lock); +EXPORT_SYMBOL(enable_dma); +EXPORT_SYMBOL(disable_dma); +EXPORT_SYMBOL(set_dma_mode); +EXPORT_SYMBOL(set_dma_addr); +EXPORT_SYMBOL(set_dma_count); +EXPORT_SYMBOL(get_dma_residue); + diff --git a/arch/sh64/kernel/early_printk.c b/arch/sh64/kernel/early_printk.c new file mode 100644 index 00000000000..8c8a76e180a --- /dev/null +++ b/arch/sh64/kernel/early_printk.c @@ -0,0 +1,105 @@ +/* + * arch/sh64/kernel/early_printk.c + * + * SH-5 Early SCIF console (cloned and hacked from sh implementation) + * + * Copyright (C) 2003, 2004 Paul Mundt <lethal@linux-sh.org> + * Copyright (C) 2002 M. R. Brown <mrbrown@0xd6.org> + * + * 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/console.h> +#include <linux/tty.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/hardware.h> + +#define SCIF_BASE_ADDR 0x01030000 +#define SCIF_ADDR_SH5 PHYS_PERIPHERAL_BLOCK+SCIF_BASE_ADDR + +/* + * Fixed virtual address where SCIF is mapped (should already be done + * in arch/sh64/kernel/head.S!). + */ +#define SCIF_REG 0xfa030000 + +enum { + SCIF_SCSMR2 = SCIF_REG + 0x00, + SCIF_SCBRR2 = SCIF_REG + 0x04, + SCIF_SCSCR2 = SCIF_REG + 0x08, + SCIF_SCFTDR2 = SCIF_REG + 0x0c, + SCIF_SCFSR2 = SCIF_REG + 0x10, + SCIF_SCFRDR2 = SCIF_REG + 0x14, + SCIF_SCFCR2 = SCIF_REG + 0x18, + SCIF_SCFDR2 = SCIF_REG + 0x1c, + SCIF_SCSPTR2 = SCIF_REG + 0x20, + SCIF_SCLSR2 = SCIF_REG + 0x24, +}; + +static void sh_console_putc(int c) +{ + while (!(ctrl_inw(SCIF_SCFSR2) & 0x20)) + cpu_relax(); + + ctrl_outb(c, SCIF_SCFTDR2); + ctrl_outw((ctrl_inw(SCIF_SCFSR2) & 0x9f), SCIF_SCFSR2); + + if (c == '\n') + sh_console_putc('\r'); +} + +static void sh_console_flush(void) +{ + ctrl_outw((ctrl_inw(SCIF_SCFSR2) & 0xbf), SCIF_SCFSR2); + + while (!(ctrl_inw(SCIF_SCFSR2) & 0x40)) + cpu_relax(); + + ctrl_outw((ctrl_inw(SCIF_SCFSR2) & 0xbf), SCIF_SCFSR2); +} + +static void sh_console_write(struct console *con, const char *s, unsigned count) +{ + while (count-- > 0) + sh_console_putc(*s++); + + sh_console_flush(); +} + +static int __init sh_console_setup(struct console *con, char *options) +{ + con->cflag = CREAD | HUPCL | CLOCAL | B19200 | CS8; + + return 0; +} + +static struct console sh_console = { + .name = "scifcon", + .write = sh_console_write, + .setup = sh_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +void __init enable_early_printk(void) +{ + ctrl_outb(0x2a, SCIF_SCBRR2); /* 19200bps */ + + ctrl_outw(0x04, SCIF_SCFCR2); /* Reset TFRST */ + ctrl_outw(0x10, SCIF_SCFCR2); /* TTRG0=1 */ + + ctrl_outw(0, SCIF_SCSPTR2); + ctrl_outw(0x60, SCIF_SCFSR2); + ctrl_outw(0, SCIF_SCLSR2); + ctrl_outw(0x30, SCIF_SCSCR2); + + register_console(&sh_console); +} + +void disable_early_printk(void) +{ + unregister_console(&sh_console); +} + diff --git a/arch/sh64/kernel/entry.S b/arch/sh64/kernel/entry.S new file mode 100644 index 00000000000..2e2cfe20b42 --- /dev/null +++ b/arch/sh64/kernel/entry.S @@ -0,0 +1,2103 @@ +/* + * 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. + * + * arch/sh64/kernel/entry.S + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2004, 2005 Paul Mundt + * Copyright (C) 2003, 2004 Richard Curnow + * + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/sys.h> + +#include <asm/processor.h> +#include <asm/registers.h> +#include <asm/unistd.h> +#include <asm/thread_info.h> +#include <asm/asm-offsets.h> + +/* + * SR fields. + */ +#define SR_ASID_MASK 0x00ff0000 +#define SR_FD_MASK 0x00008000 +#define SR_SS 0x08000000 +#define SR_BL 0x10000000 +#define SR_MD 0x40000000 + +/* + * Event code. + */ +#define EVENT_INTERRUPT 0 +#define EVENT_FAULT_TLB 1 +#define EVENT_FAULT_NOT_TLB 2 +#define EVENT_DEBUG 3 + +/* EXPEVT values */ +#define RESET_CAUSE 0x20 +#define DEBUGSS_CAUSE 0x980 + +/* + * Frame layout. Quad index. + */ +#define FRAME_T(x) FRAME_TBASE+(x*8) +#define FRAME_R(x) FRAME_RBASE+(x*8) +#define FRAME_S(x) FRAME_SBASE+(x*8) +#define FSPC 0 +#define FSSR 1 +#define FSYSCALL_ID 2 + +/* Arrange the save frame to be a multiple of 32 bytes long */ +#define FRAME_SBASE 0 +#define FRAME_RBASE (FRAME_SBASE+(3*8)) /* SYSCALL_ID - SSR - SPC */ +#define FRAME_TBASE (FRAME_RBASE+(63*8)) /* r0 - r62 */ +#define FRAME_PBASE (FRAME_TBASE+(8*8)) /* tr0 -tr7 */ +#define FRAME_SIZE (FRAME_PBASE+(2*8)) /* pad0-pad1 */ + +#define FP_FRAME_SIZE FP_FRAME_BASE+(33*8) /* dr0 - dr31 + fpscr */ +#define FP_FRAME_BASE 0 + +#define SAVED_R2 0*8 +#define SAVED_R3 1*8 +#define SAVED_R4 2*8 +#define SAVED_R5 3*8 +#define SAVED_R18 4*8 +#define SAVED_R6 5*8 +#define SAVED_TR0 6*8 + +/* These are the registers saved in the TLB path that aren't saved in the first + level of the normal one. */ +#define TLB_SAVED_R25 7*8 +#define TLB_SAVED_TR1 8*8 +#define TLB_SAVED_TR2 9*8 +#define TLB_SAVED_TR3 10*8 +#define TLB_SAVED_TR4 11*8 +/* Save R0/R1 : PT-migrating compiler currently dishounours -ffixed-r0 and -ffixed-r1 causing + breakage otherwise. */ +#define TLB_SAVED_R0 12*8 +#define TLB_SAVED_R1 13*8 + +#define CLI() \ + getcon SR, r6; \ + ori r6, 0xf0, r6; \ + putcon r6, SR; + +#define STI() \ + getcon SR, r6; \ + andi r6, ~0xf0, r6; \ + putcon r6, SR; + +#ifdef CONFIG_PREEMPT +# define preempt_stop() CLI() +#else +# define preempt_stop() +# define resume_kernel restore_all +#endif + + .section .data, "aw" + +#define FAST_TLBMISS_STACK_CACHELINES 4 +#define FAST_TLBMISS_STACK_QUADWORDS (4*FAST_TLBMISS_STACK_CACHELINES) + +/* Register back-up area for all exceptions */ + .balign 32 + /* Allow for 16 quadwords to be pushed by fast tlbmiss handling + * register saves etc. */ + .fill FAST_TLBMISS_STACK_QUADWORDS, 8, 0x0 +/* This is 32 byte aligned by construction */ +/* Register back-up area for all exceptions */ +reg_save_area: + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .quad 0 + .quad 0 + +/* Save area for RESVEC exceptions. We cannot use reg_save_area because of + * reentrancy. Note this area may be accessed via physical address. + * Align so this fits a whole single cache line, for ease of purging. + */ + .balign 32,0,32 +resvec_save_area: + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .balign 32,0,32 + +/* Jump table of 3rd level handlers */ +trap_jtable: + .long do_exception_error /* 0x000 */ + .long do_exception_error /* 0x020 */ + .long tlb_miss_load /* 0x040 */ + .long tlb_miss_store /* 0x060 */ + ! ARTIFICIAL pseudo-EXPEVT setting + .long do_debug_interrupt /* 0x080 */ + .long tlb_miss_load /* 0x0A0 */ + .long tlb_miss_store /* 0x0C0 */ + .long do_address_error_load /* 0x0E0 */ + .long do_address_error_store /* 0x100 */ +#ifdef CONFIG_SH_FPU + .long do_fpu_error /* 0x120 */ +#else + .long do_exception_error /* 0x120 */ +#endif + .long do_exception_error /* 0x140 */ + .long system_call /* 0x160 */ + .long do_reserved_inst /* 0x180 */ + .long do_illegal_slot_inst /* 0x1A0 */ + .long do_NMI /* 0x1C0 */ + .long do_exception_error /* 0x1E0 */ + .rept 15 + .long do_IRQ /* 0x200 - 0x3C0 */ + .endr + .long do_exception_error /* 0x3E0 */ + .rept 32 + .long do_IRQ /* 0x400 - 0x7E0 */ + .endr + .long fpu_error_or_IRQA /* 0x800 */ + .long fpu_error_or_IRQB /* 0x820 */ + .long do_IRQ /* 0x840 */ + .long do_IRQ /* 0x860 */ + .rept 6 + .long do_exception_error /* 0x880 - 0x920 */ + .endr + .long do_software_break_point /* 0x940 */ + .long do_exception_error /* 0x960 */ + .long do_single_step /* 0x980 */ + + .rept 3 + .long do_exception_error /* 0x9A0 - 0x9E0 */ + .endr + .long do_IRQ /* 0xA00 */ + .long do_IRQ /* 0xA20 */ + .long itlb_miss_or_IRQ /* 0xA40 */ + .long do_IRQ /* 0xA60 */ + .long do_IRQ /* 0xA80 */ + .long itlb_miss_or_IRQ /* 0xAA0 */ + .long do_exception_error /* 0xAC0 */ + .long do_address_error_exec /* 0xAE0 */ + .rept 8 + .long do_exception_error /* 0xB00 - 0xBE0 */ + .endr + .rept 18 + .long do_IRQ /* 0xC00 - 0xE20 */ + .endr + + .section .text64, "ax" + +/* + * --- Exception/Interrupt/Event Handling Section + */ + +/* + * VBR and RESVEC blocks. + * + * First level handler for VBR-based exceptions. + * + * To avoid waste of space, align to the maximum text block size. + * This is assumed to be at most 128 bytes or 32 instructions. + * DO NOT EXCEED 32 instructions on the first level handlers ! + * + * Also note that RESVEC is contained within the VBR block + * where the room left (1KB - TEXT_SIZE) allows placing + * the RESVEC block (at most 512B + TEXT_SIZE). + * + * So first (and only) level handler for RESVEC-based exceptions. + * + * Where the fault/interrupt is handled (not_a_tlb_miss, tlb_miss + * and interrupt) we are a lot tight with register space until + * saving onto the stack frame, which is done in handle_exception(). + * + */ + +#define TEXT_SIZE 128 +#define BLOCK_SIZE 1664 /* Dynamic check, 13*128 */ + + .balign TEXT_SIZE +LVBR_block: + .space 256, 0 /* Power-on class handler, */ + /* not required here */ +not_a_tlb_miss: + synco /* TAKum03020 (but probably a good idea anyway.) */ + /* Save original stack pointer into KCR1 */ + putcon SP, KCR1 + + /* Save other original registers into reg_save_area */ + movi reg_save_area, SP + st.q SP, SAVED_R2, r2 + st.q SP, SAVED_R3, r3 + st.q SP, SAVED_R4, r4 + st.q SP, SAVED_R5, r5 + st.q SP, SAVED_R6, r6 + st.q SP, SAVED_R18, r18 + gettr tr0, r3 + st.q SP, SAVED_TR0, r3 + + /* Set args for Non-debug, Not a TLB miss class handler */ + getcon EXPEVT, r2 + movi ret_from_exception, r3 + ori r3, 1, r3 + movi EVENT_FAULT_NOT_TLB, r4 + or SP, ZERO, r5 + getcon KCR1, SP + pta handle_exception, tr0 + blink tr0, ZERO + + .balign 256 + ! VBR+0x200 + nop + .balign 256 + ! VBR+0x300 + nop + .balign 256 + /* + * Instead of the natural .balign 1024 place RESVEC here + * respecting the final 1KB alignment. + */ + .balign TEXT_SIZE + /* + * Instead of '.space 1024-TEXT_SIZE' place the RESVEC + * block making sure the final alignment is correct. + */ +tlb_miss: + synco /* TAKum03020 (but probably a good idea anyway.) */ + putcon SP, KCR1 + movi reg_save_area, SP + /* SP is guaranteed 32-byte aligned. */ + st.q SP, TLB_SAVED_R0 , r0 + st.q SP, TLB_SAVED_R1 , r1 + st.q SP, SAVED_R2 , r2 + st.q SP, SAVED_R3 , r3 + st.q SP, SAVED_R4 , r4 + st.q SP, SAVED_R5 , r5 + st.q SP, SAVED_R6 , r6 + st.q SP, SAVED_R18, r18 + + /* Save R25 for safety; as/ld may want to use it to achieve the call to + * the code in mm/tlbmiss.c */ + st.q SP, TLB_SAVED_R25, r25 + gettr tr0, r2 + gettr tr1, r3 + gettr tr2, r4 + gettr tr3, r5 + gettr tr4, r18 + st.q SP, SAVED_TR0 , r2 + st.q SP, TLB_SAVED_TR1 , r3 + st.q SP, TLB_SAVED_TR2 , r4 + st.q SP, TLB_SAVED_TR3 , r5 + st.q SP, TLB_SAVED_TR4 , r18 + + pt do_fast_page_fault, tr0 + getcon SSR, r2 + getcon EXPEVT, r3 + getcon TEA, r4 + shlri r2, 30, r2 + andi r2, 1, r2 /* r2 = SSR.MD */ + blink tr0, LINK + + pt fixup_to_invoke_general_handler, tr1 + + /* If the fast path handler fixed the fault, just drop through quickly + to the restore code right away to return to the excepting context. + */ + beqi/u r2, 0, tr1 + +fast_tlb_miss_restore: + ld.q SP, SAVED_TR0, r2 + ld.q SP, TLB_SAVED_TR1, r3 + ld.q SP, TLB_SAVED_TR2, r4 + + ld.q SP, TLB_SAVED_TR3, r5 + ld.q SP, TLB_SAVED_TR4, r18 + + ptabs r2, tr0 + ptabs r3, tr1 + ptabs r4, tr2 + ptabs r5, tr3 + ptabs r18, tr4 + + ld.q SP, TLB_SAVED_R0, r0 + ld.q SP, TLB_SAVED_R1, r1 + ld.q SP, SAVED_R2, r2 + ld.q SP, SAVED_R3, r3 + ld.q SP, SAVED_R4, r4 + ld.q SP, SAVED_R5, r5 + ld.q SP, SAVED_R6, r6 + ld.q SP, SAVED_R18, r18 + ld.q SP, TLB_SAVED_R25, r25 + + getcon KCR1, SP + rte + nop /* for safety, in case the code is run on sh5-101 cut1.x */ + +fixup_to_invoke_general_handler: + + /* OK, new method. Restore stuff that's not expected to get saved into + the 'first-level' reg save area, then just fall through to setting + up the registers and calling the second-level handler. */ + + /* 2nd level expects r2,3,4,5,6,18,tr0 to be saved. So we must restore + r25,tr1-4 and save r6 to get into the right state. */ + + ld.q SP, TLB_SAVED_TR1, r3 + ld.q SP, TLB_SAVED_TR2, r4 + ld.q SP, TLB_SAVED_TR3, r5 + ld.q SP, TLB_SAVED_TR4, r18 + ld.q SP, TLB_SAVED_R25, r25 + + ld.q SP, TLB_SAVED_R0, r0 + ld.q SP, TLB_SAVED_R1, r1 + + ptabs/u r3, tr1 + ptabs/u r4, tr2 + ptabs/u r5, tr3 + ptabs/u r18, tr4 + + /* Set args for Non-debug, TLB miss class handler */ + getcon EXPEVT, r2 + movi ret_from_exception, r3 + ori r3, 1, r3 + movi EVENT_FAULT_TLB, r4 + or SP, ZERO, r5 + getcon KCR1, SP + pta handle_exception, tr0 + blink tr0, ZERO + +/* NB TAKE GREAT CARE HERE TO ENSURE THAT THE INTERRUPT CODE + DOES END UP AT VBR+0x600 */ + nop + nop + nop + nop + nop + nop + + .balign 256 + /* VBR + 0x600 */ + +interrupt: + synco /* TAKum03020 (but probably a good idea anyway.) */ + /* Save original stack pointer into KCR1 */ + putcon SP, KCR1 + + /* Save other original registers into reg_save_area */ + movi reg_save_area, SP + st.q SP, SAVED_R2, r2 + st.q SP, SAVED_R3, r3 + st.q SP, SAVED_R4, r4 + st.q SP, SAVED_R5, r5 + st.q SP, SAVED_R6, r6 + st.q SP, SAVED_R18, r18 + gettr tr0, r3 + st.q SP, SAVED_TR0, r3 + + /* Set args for interrupt class handler */ + getcon INTEVT, r2 + movi ret_from_irq, r3 + ori r3, 1, r3 + movi EVENT_INTERRUPT, r4 + or SP, ZERO, r5 + getcon KCR1, SP + pta handle_exception, tr0 + blink tr0, ZERO + .balign TEXT_SIZE /* let's waste the bare minimum */ + +LVBR_block_end: /* Marker. Used for total checking */ + + .balign 256 +LRESVEC_block: + /* Panic handler. Called with MMU off. Possible causes/actions: + * - Reset: Jump to program start. + * - Single Step: Turn off Single Step & return. + * - Others: Call panic handler, passing PC as arg. + * (this may need to be extended...) + */ +reset_or_panic: + synco /* TAKum03020 (but probably a good idea anyway.) */ + putcon SP, DCR + /* First save r0-1 and tr0, as we need to use these */ + movi resvec_save_area-CONFIG_CACHED_MEMORY_OFFSET, SP + st.q SP, 0, r0 + st.q SP, 8, r1 + gettr tr0, r0 + st.q SP, 32, r0 + + /* Check cause */ + getcon EXPEVT, r0 + movi RESET_CAUSE, r1 + sub r1, r0, r1 /* r1=0 if reset */ + movi _stext-CONFIG_CACHED_MEMORY_OFFSET, r0 + ori r0, 1, r0 + ptabs r0, tr0 + beqi r1, 0, tr0 /* Jump to start address if reset */ + + getcon EXPEVT, r0 + movi DEBUGSS_CAUSE, r1 + sub r1, r0, r1 /* r1=0 if single step */ + pta single_step_panic, tr0 + beqi r1, 0, tr0 /* jump if single step */ + + /* Now jump to where we save the registers. */ + movi panic_stash_regs-CONFIG_CACHED_MEMORY_OFFSET, r1 + ptabs r1, tr0 + blink tr0, r63 + +single_step_panic: + /* We are in a handler with Single Step set. We need to resume the + * handler, by turning on MMU & turning off Single Step. */ + getcon SSR, r0 + movi SR_MMU, r1 + or r0, r1, r0 + movi ~SR_SS, r1 + and r0, r1, r0 + putcon r0, SSR + /* Restore EXPEVT, as the rte won't do this */ + getcon PEXPEVT, r0 + putcon r0, EXPEVT + /* Restore regs */ + ld.q SP, 32, r0 + ptabs r0, tr0 + ld.q SP, 0, r0 + ld.q SP, 8, r1 + getcon DCR, SP + synco + rte + + + .balign 256 +debug_exception: + synco /* TAKum03020 (but probably a good idea anyway.) */ + /* + * Single step/software_break_point first level handler. + * Called with MMU off, so the first thing we do is enable it + * by doing an rte with appropriate SSR. + */ + putcon SP, DCR + /* Save SSR & SPC, together with R0 & R1, as we need to use 2 regs. */ + movi resvec_save_area-CONFIG_CACHED_MEMORY_OFFSET, SP + + /* With the MMU off, we are bypassing the cache, so purge any + * data that will be made stale by the following stores. + */ + ocbp SP, 0 + synco + + st.q SP, 0, r0 + st.q SP, 8, r1 + getcon SPC, r0 + st.q SP, 16, r0 + getcon SSR, r0 + st.q SP, 24, r0 + + /* Enable MMU, block exceptions, set priv mode, disable single step */ + movi SR_MMU | SR_BL | SR_MD, r1 + or r0, r1, r0 + movi ~SR_SS, r1 + and r0, r1, r0 + putcon r0, SSR + /* Force control to debug_exception_2 when rte is executed */ + movi debug_exeception_2, r0 + ori r0, 1, r0 /* force SHmedia, just in case */ + putcon r0, SPC + getcon DCR, SP + synco + rte +debug_exeception_2: + /* Restore saved regs */ + putcon SP, KCR1 + movi resvec_save_area, SP + ld.q SP, 24, r0 + putcon r0, SSR + ld.q SP, 16, r0 + putcon r0, SPC + ld.q SP, 0, r0 + ld.q SP, 8, r1 + + /* Save other original registers into reg_save_area */ + movi reg_save_area, SP + st.q SP, SAVED_R2, r2 + st.q SP, SAVED_R3, r3 + st.q SP, SAVED_R4, r4 + st.q SP, SAVED_R5, r5 + st.q SP, SAVED_R6, r6 + st.q SP, SAVED_R18, r18 + gettr tr0, r3 + st.q SP, SAVED_TR0, r3 + + /* Set args for debug class handler */ + getcon EXPEVT, r2 + movi ret_from_exception, r3 + ori r3, 1, r3 + movi EVENT_DEBUG, r4 + or SP, ZERO, r5 + getcon KCR1, SP + pta handle_exception, tr0 + blink tr0, ZERO + + .balign 256 +debug_interrupt: + /* !!! WE COME HERE IN REAL MODE !!! */ + /* Hook-up debug interrupt to allow various debugging options to be + * hooked into its handler. */ + /* Save original stack pointer into KCR1 */ + synco + putcon SP, KCR1 + movi resvec_save_area-CONFIG_CACHED_MEMORY_OFFSET, SP + ocbp SP, 0 + ocbp SP, 32 + synco + + /* Save other original registers into reg_save_area thru real addresses */ + st.q SP, SAVED_R2, r2 + st.q SP, SAVED_R3, r3 + st.q SP, SAVED_R4, r4 + st.q SP, SAVED_R5, r5 + st.q SP, SAVED_R6, r6 + st.q SP, SAVED_R18, r18 + gettr tr0, r3 + st.q SP, SAVED_TR0, r3 + + /* move (spc,ssr)->(pspc,pssr). The rte will shift + them back again, so that they look like the originals + as far as the real handler code is concerned. */ + getcon spc, r6 + putcon r6, pspc + getcon ssr, r6 + putcon r6, pssr + + ! construct useful SR for handle_exception + movi 3, r6 + shlli r6, 30, r6 + getcon sr, r18 + or r18, r6, r6 + putcon r6, ssr + + ! SSR is now the current SR with the MD and MMU bits set + ! i.e. the rte will switch back to priv mode and put + ! the mmu back on + + ! construct spc + movi handle_exception, r18 + ori r18, 1, r18 ! for safety (do we need this?) + putcon r18, spc + + /* Set args for Non-debug, Not a TLB miss class handler */ + + ! EXPEVT==0x80 is unused, so 'steal' this value to put the + ! debug interrupt handler in the vectoring table + movi 0x80, r2 + movi ret_from_exception, r3 + ori r3, 1, r3 + movi EVENT_FAULT_NOT_TLB, r4 + + or SP, ZERO, r5 + movi CONFIG_CACHED_MEMORY_OFFSET, r6 + add r6, r5, r5 + getcon KCR1, SP + + synco ! for safety + rte ! -> handle_exception, switch back to priv mode again + +LRESVEC_block_end: /* Marker. Unused. */ + + .balign TEXT_SIZE + +/* + * Second level handler for VBR-based exceptions. Pre-handler. + * In common to all stack-frame sensitive handlers. + * + * Inputs: + * (KCR0) Current [current task union] + * (KCR1) Original SP + * (r2) INTEVT/EXPEVT + * (r3) appropriate return address + * (r4) Event (0 = interrupt, 1 = TLB miss fault, 2 = Not TLB miss fault, 3=debug) + * (r5) Pointer to reg_save_area + * (SP) Original SP + * + * Available registers: + * (r6) + * (r18) + * (tr0) + * + */ +handle_exception: + /* Common 2nd level handler. */ + + /* First thing we need an appropriate stack pointer */ + getcon SSR, r6 + shlri r6, 30, r6 + andi r6, 1, r6 + pta stack_ok, tr0 + bne r6, ZERO, tr0 /* Original stack pointer is fine */ + + /* Set stack pointer for user fault */ + getcon KCR0, SP + movi THREAD_SIZE, r6 /* Point to the end */ + add SP, r6, SP + +stack_ok: + +/* DEBUG : check for underflow/overflow of the kernel stack */ + pta no_underflow, tr0 + getcon KCR0, r6 + movi 1024, r18 + add r6, r18, r6 + bge SP, r6, tr0 ! ? below 1k from bottom of stack : danger zone + +/* Just panic to cause a crash. */ +bad_sp: + ld.b r63, 0, r6 + nop + +no_underflow: + pta bad_sp, tr0 + getcon kcr0, r6 + movi THREAD_SIZE, r18 + add r18, r6, r6 + bgt SP, r6, tr0 ! sp above the stack + + /* Make some room for the BASIC frame. */ + movi -(FRAME_SIZE), r6 + add SP, r6, SP + +/* Could do this with no stalling if we had another spare register, but the + code below will be OK. */ + ld.q r5, SAVED_R2, r6 + ld.q r5, SAVED_R3, r18 + st.q SP, FRAME_R(2), r6 + ld.q r5, SAVED_R4, r6 + st.q SP, FRAME_R(3), r18 + ld.q r5, SAVED_R5, r18 + st.q SP, FRAME_R(4), r6 + ld.q r5, SAVED_R6, r6 + st.q SP, FRAME_R(5), r18 + ld.q r5, SAVED_R18, r18 + st.q SP, FRAME_R(6), r6 + ld.q r5, SAVED_TR0, r6 + st.q SP, FRAME_R(18), r18 + st.q SP, FRAME_T(0), r6 + + /* Keep old SP around */ + getcon KCR1, r6 + + /* Save the rest of the general purpose registers */ + st.q SP, FRAME_R(0), r0 + st.q SP, FRAME_R(1), r1 + st.q SP, FRAME_R(7), r7 + st.q SP, FRAME_R(8), r8 + st.q SP, FRAME_R(9), r9 + st.q SP, FRAME_R(10), r10 + st.q SP, FRAME_R(11), r11 + st.q SP, FRAME_R(12), r12 + st.q SP, FRAME_R(13), r13 + st.q SP, FRAME_R(14), r14 + + /* SP is somewhere else */ + st.q SP, FRAME_R(15), r6 + + st.q SP, FRAME_R(16), r16 + st.q SP, FRAME_R(17), r17 + /* r18 is saved earlier. */ + st.q SP, FRAME_R(19), r19 + st.q SP, FRAME_R(20), r20 + st.q SP, FRAME_R(21), r21 + st.q SP, FRAME_R(22), r22 + st.q SP, FRAME_R(23), r23 + st.q SP, FRAME_R(24), r24 + st.q SP, FRAME_R(25), r25 + st.q SP, FRAME_R(26), r26 + st.q SP, FRAME_R(27), r27 + st.q SP, FRAME_R(28), r28 + st.q SP, FRAME_R(29), r29 + st.q SP, FRAME_R(30), r30 + st.q SP, FRAME_R(31), r31 + st.q SP, FRAME_R(32), r32 + st.q SP, FRAME_R(33), r33 + st.q SP, FRAME_R(34), r34 + st.q SP, FRAME_R(35), r35 + st.q SP, FRAME_R(36), r36 + st.q SP, FRAME_R(37), r37 + st.q SP, FRAME_R(38), r38 + st.q SP, FRAME_R(39), r39 + st.q SP, FRAME_R(40), r40 + st.q SP, FRAME_R(41), r41 + st.q SP, FRAME_R(42), r42 + st.q SP, FRAME_R(43), r43 + st.q SP, FRAME_R(44), r44 + st.q SP, FRAME_R(45), r45 + st.q SP, FRAME_R(46), r46 + st.q SP, FRAME_R(47), r47 + st.q SP, FRAME_R(48), r48 + st.q SP, FRAME_R(49), r49 + st.q SP, FRAME_R(50), r50 + st.q SP, FRAME_R(51), r51 + st.q SP, FRAME_R(52), r52 + st.q SP, FRAME_R(53), r53 + st.q SP, FRAME_R(54), r54 + st.q SP, FRAME_R(55), r55 + st.q SP, FRAME_R(56), r56 + st.q SP, FRAME_R(57), r57 + st.q SP, FRAME_R(58), r58 + st.q SP, FRAME_R(59), r59 + st.q SP, FRAME_R(60), r60 + st.q SP, FRAME_R(61), r61 + st.q SP, FRAME_R(62), r62 + + /* + * Save the S* registers. + */ + getcon SSR, r61 + st.q SP, FRAME_S(FSSR), r61 + getcon SPC, r62 + st.q SP, FRAME_S(FSPC), r62 + movi -1, r62 /* Reset syscall_nr */ + st.q SP, FRAME_S(FSYSCALL_ID), r62 + + /* Save the rest of the target registers */ + gettr tr1, r6 + st.q SP, FRAME_T(1), r6 + gettr tr2, r6 + st.q SP, FRAME_T(2), r6 + gettr tr3, r6 + st.q SP, FRAME_T(3), r6 + gettr tr4, r6 + st.q SP, FRAME_T(4), r6 + gettr tr5, r6 + st.q SP, FRAME_T(5), r6 + gettr tr6, r6 + st.q SP, FRAME_T(6), r6 + gettr tr7, r6 + st.q SP, FRAME_T(7), r6 + + ! setup FP so that unwinder can wind back through nested kernel mode + ! exceptions + add SP, ZERO, r14 + +#ifdef CONFIG_POOR_MANS_STRACE + /* We've pushed all the registers now, so only r2-r4 hold anything + * useful. Move them into callee save registers */ + or r2, ZERO, r28 + or r3, ZERO, r29 + or r4, ZERO, r30 + + /* Preserve r2 as the event code */ + movi evt_debug, r3 + ori r3, 1, r3 + ptabs r3, tr0 + + or SP, ZERO, r6 + getcon TRA, r5 + blink tr0, LINK + + or r28, ZERO, r2 + or r29, ZERO, r3 + or r30, ZERO, r4 +#endif + + /* For syscall and debug race condition, get TRA now */ + getcon TRA, r5 + + /* We are in a safe position to turn SR.BL off, but set IMASK=0xf + * Also set FD, to catch FPU usage in the kernel. + * + * benedict.gaster@superh.com 29/07/2002 + * + * On all SH5-101 revisions it is unsafe to raise the IMASK and at the + * same time change BL from 1->0, as any pending interrupt of a level + * higher than he previous value of IMASK will leak through and be + * taken unexpectedly. + * + * To avoid this we raise the IMASK and then issue another PUTCON to + * enable interrupts. + */ + getcon SR, r6 + movi SR_IMASK | SR_FD, r7 + or r6, r7, r6 + putcon r6, SR + movi SR_UNBLOCK_EXC, r7 + and r6, r7, r6 + putcon r6, SR + + + /* Now call the appropriate 3rd level handler */ + or r3, ZERO, LINK + movi trap_jtable, r3 + shlri r2, 3, r2 + ldx.l r2, r3, r3 + shlri r2, 2, r2 + ptabs r3, tr0 + or SP, ZERO, r3 + blink tr0, ZERO + +/* + * Second level handler for VBR-based exceptions. Post-handlers. + * + * Post-handlers for interrupts (ret_from_irq), exceptions + * (ret_from_exception) and common reentrance doors (restore_all + * to get back to the original context, ret_from_syscall loop to + * check kernel exiting). + * + * ret_with_reschedule and work_notifysig are an inner lables of + * the ret_from_syscall loop. + * + * In common to all stack-frame sensitive handlers. + * + * Inputs: + * (SP) struct pt_regs *, original register's frame pointer (basic) + * + */ + .global ret_from_irq +ret_from_irq: +#ifdef CONFIG_POOR_MANS_STRACE + pta evt_debug_ret_from_irq, tr0 + ori SP, 0, r2 + blink tr0, LINK +#endif + ld.q SP, FRAME_S(FSSR), r6 + shlri r6, 30, r6 + andi r6, 1, r6 + pta resume_kernel, tr0 + bne r6, ZERO, tr0 /* no further checks */ + STI() + pta ret_with_reschedule, tr0 + blink tr0, ZERO /* Do not check softirqs */ + + .global ret_from_exception +ret_from_exception: + preempt_stop() + +#ifdef CONFIG_POOR_MANS_STRACE + pta evt_debug_ret_from_exc, tr0 + ori SP, 0, r2 + blink tr0, LINK +#endif + + ld.q SP, FRAME_S(FSSR), r6 + shlri r6, 30, r6 + andi r6, 1, r6 + pta resume_kernel, tr0 + bne r6, ZERO, tr0 /* no further checks */ + + /* Check softirqs */ + +#ifdef CONFIG_PREEMPT + pta ret_from_syscall, tr0 + blink tr0, ZERO + +resume_kernel: + pta restore_all, tr0 + + getcon KCR0, r6 + ld.l r6, TI_PRE_COUNT, r7 + beq/u r7, ZERO, tr0 + +need_resched: + ld.l r6, TI_FLAGS, r7 + movi (1 << TIF_NEED_RESCHED), r8 + and r8, r7, r8 + bne r8, ZERO, tr0 + + getcon SR, r7 + andi r7, 0xf0, r7 + bne r7, ZERO, tr0 + + movi ((PREEMPT_ACTIVE >> 16) & 65535), r8 + shori (PREEMPT_ACTIVE & 65535), r8 + st.l r6, TI_PRE_COUNT, r8 + + STI() + movi schedule, r7 + ori r7, 1, r7 + ptabs r7, tr1 + blink tr1, LINK + + st.l r6, TI_PRE_COUNT, ZERO + CLI() + + pta need_resched, tr1 + blink tr1, ZERO +#endif + + .global ret_from_syscall +ret_from_syscall: + +ret_with_reschedule: + getcon KCR0, r6 ! r6 contains current_thread_info + ld.l r6, TI_FLAGS, r7 ! r7 contains current_thread_info->flags + + ! FIXME:!!! + ! no handling of TIF_SYSCALL_TRACE yet!! + + movi (1 << TIF_NEED_RESCHED), r8 + and r8, r7, r8 + pta work_resched, tr0 + bne r8, ZERO, tr0 + + pta restore_all, tr1 + + movi (1 << TIF_SIGPENDING), r8 + and r8, r7, r8 + pta work_notifysig, tr0 + bne r8, ZERO, tr0 + + blink tr1, ZERO + +work_resched: + pta ret_from_syscall, tr0 + gettr tr0, LINK + movi schedule, r6 + ptabs r6, tr0 + blink tr0, ZERO /* Call schedule(), return on top */ + +work_notifysig: + gettr tr1, LINK + + movi do_signal, r6 + ptabs r6, tr0 + or SP, ZERO, r2 + or ZERO, ZERO, r3 + blink tr0, LINK /* Call do_signal(regs, 0), return here */ + +restore_all: + /* Do prefetches */ + + ld.q SP, FRAME_T(0), r6 + ld.q SP, FRAME_T(1), r7 + ld.q SP, FRAME_T(2), r8 + ld.q SP, FRAME_T(3), r9 + ptabs r6, tr0 + ptabs r7, tr1 + ptabs r8, tr2 + ptabs r9, tr3 + ld.q SP, FRAME_T(4), r6 + ld.q SP, FRAME_T(5), r7 + ld.q SP, FRAME_T(6), r8 + ld.q SP, FRAME_T(7), r9 + ptabs r6, tr4 + ptabs r7, tr5 + ptabs r8, tr6 + ptabs r9, tr7 + + ld.q SP, FRAME_R(0), r0 + ld.q SP, FRAME_R(1), r1 + ld.q SP, FRAME_R(2), r2 + ld.q SP, FRAME_R(3), r3 + ld.q SP, FRAME_R(4), r4 + ld.q SP, FRAME_R(5), r5 + ld.q SP, FRAME_R(6), r6 + ld.q SP, FRAME_R(7), r7 + ld.q SP, FRAME_R(8), r8 + ld.q SP, FRAME_R(9), r9 + ld.q SP, FRAME_R(10), r10 + ld.q SP, FRAME_R(11), r11 + ld.q SP, FRAME_R(12), r12 + ld.q SP, FRAME_R(13), r13 + ld.q SP, FRAME_R(14), r14 + + ld.q SP, FRAME_R(16), r16 + ld.q SP, FRAME_R(17), r17 + ld.q SP, FRAME_R(18), r18 + ld.q SP, FRAME_R(19), r19 + ld.q SP, FRAME_R(20), r20 + ld.q SP, FRAME_R(21), r21 + ld.q SP, FRAME_R(22), r22 + ld.q SP, FRAME_R(23), r23 + ld.q SP, FRAME_R(24), r24 + ld.q SP, FRAME_R(25), r25 + ld.q SP, FRAME_R(26), r26 + ld.q SP, FRAME_R(27), r27 + ld.q SP, FRAME_R(28), r28 + ld.q SP, FRAME_R(29), r29 + ld.q SP, FRAME_R(30), r30 + ld.q SP, FRAME_R(31), r31 + ld.q SP, FRAME_R(32), r32 + ld.q SP, FRAME_R(33), r33 + ld.q SP, FRAME_R(34), r34 + ld.q SP, FRAME_R(35), r35 + ld.q SP, FRAME_R(36), r36 + ld.q SP, FRAME_R(37), r37 + ld.q SP, FRAME_R(38), r38 + ld.q SP, FRAME_R(39), r39 + ld.q SP, FRAME_R(40), r40 + ld.q SP, FRAME_R(41), r41 + ld.q SP, FRAME_R(42), r42 + ld.q SP, FRAME_R(43), r43 + ld.q SP, FRAME_R(44), r44 + ld.q SP, FRAME_R(45), r45 + ld.q SP, FRAME_R(46), r46 + ld.q SP, FRAME_R(47), r47 + ld.q SP, FRAME_R(48), r48 + ld.q SP, FRAME_R(49), r49 + ld.q SP, FRAME_R(50), r50 + ld.q SP, FRAME_R(51), r51 + ld.q SP, FRAME_R(52), r52 + ld.q SP, FRAME_R(53), r53 + ld.q SP, FRAME_R(54), r54 + ld.q SP, FRAME_R(55), r55 + ld.q SP, FRAME_R(56), r56 + ld.q SP, FRAME_R(57), r57 + ld.q SP, FRAME_R(58), r58 + + getcon SR, r59 + movi SR_BLOCK_EXC, r60 + or r59, r60, r59 + putcon r59, SR /* SR.BL = 1, keep nesting out */ + ld.q SP, FRAME_S(FSSR), r61 + ld.q SP, FRAME_S(FSPC), r62 + movi SR_ASID_MASK, r60 + and r59, r60, r59 + andc r61, r60, r61 /* Clear out older ASID */ + or r59, r61, r61 /* Retain current ASID */ + putcon r61, SSR + putcon r62, SPC + + /* Ignore FSYSCALL_ID */ + + ld.q SP, FRAME_R(59), r59 + ld.q SP, FRAME_R(60), r60 + ld.q SP, FRAME_R(61), r61 + ld.q SP, FRAME_R(62), r62 + + /* Last touch */ + ld.q SP, FRAME_R(15), SP + rte + nop + +/* + * Third level handlers for VBR-based exceptions. Adapting args to + * and/or deflecting to fourth level handlers. + * + * Fourth level handlers interface. + * Most are C-coded handlers directly pointed by the trap_jtable. + * (Third = Fourth level) + * Inputs: + * (r2) fault/interrupt code, entry number (e.g. NMI = 14, + * IRL0-3 (0000) = 16, RTLBMISS = 2, SYSCALL = 11, etc ...) + * (r3) struct pt_regs *, original register's frame pointer + * (r4) Event (0 = interrupt, 1 = TLB miss fault, 2 = Not TLB miss fault) + * (r5) TRA control register (for syscall/debug benefit only) + * (LINK) return address + * (SP) = r3 + * + * Kernel TLB fault handlers will get a slightly different interface. + * (r2) struct pt_regs *, original register's frame pointer + * (r3) writeaccess, whether it's a store fault as opposed to load fault + * (r4) execaccess, whether it's a ITLB fault as opposed to DTLB fault + * (r5) Effective Address of fault + * (LINK) return address + * (SP) = r2 + * + * fpu_error_or_IRQ? is a helper to deflect to the right cause. + * + */ +tlb_miss_load: + or SP, ZERO, r2 + or ZERO, ZERO, r3 /* Read */ + or ZERO, ZERO, r4 /* Data */ + getcon TEA, r5 + pta call_do_page_fault, tr0 + beq ZERO, ZERO, tr0 + +tlb_miss_store: + or SP, ZERO, r2 + movi 1, r3 /* Write */ + or ZERO, ZERO, r4 /* Data */ + getcon TEA, r5 + pta call_do_page_fault, tr0 + beq ZERO, ZERO, tr0 + +itlb_miss_or_IRQ: + pta its_IRQ, tr0 + beqi/u r4, EVENT_INTERRUPT, tr0 + or SP, ZERO, r2 + or ZERO, ZERO, r3 /* Read */ + movi 1, r4 /* Text */ + getcon TEA, r5 + /* Fall through */ + +call_do_page_fault: + movi do_page_fault, r6 + ptabs r6, tr0 + blink tr0, ZERO + +fpu_error_or_IRQA: + pta its_IRQ, tr0 + beqi/l r4, EVENT_INTERRUPT, tr0 +#ifdef CONFIG_SH_FPU + movi do_fpu_state_restore, r6 +#else + movi do_exception_error, r6 +#endif + ptabs r6, tr0 + blink tr0, ZERO + +fpu_error_or_IRQB: + pta its_IRQ, tr0 + beqi/l r4, EVENT_INTERRUPT, tr0 +#ifdef CONFIG_SH_FPU + movi do_fpu_state_restore, r6 +#else + movi do_exception_error, r6 +#endif + ptabs r6, tr0 + blink tr0, ZERO + +its_IRQ: + movi do_IRQ, r6 + ptabs r6, tr0 + blink tr0, ZERO + +/* + * system_call/unknown_trap third level handler: + * + * Inputs: + * (r2) fault/interrupt code, entry number (TRAP = 11) + * (r3) struct pt_regs *, original register's frame pointer + * (r4) Not used. Event (0=interrupt, 1=TLB miss fault, 2=Not TLB miss fault) + * (r5) TRA Control Reg (0x00xyzzzz: x=1 SYSCALL, y = #args, z=nr) + * (SP) = r3 + * (LINK) return address: ret_from_exception + * (*r3) Syscall parms: SC#, arg0, arg1, ..., arg5 in order (Saved r2/r7) + * + * Outputs: + * (*r3) Syscall reply (Saved r2) + * (LINK) In case of syscall only it can be scrapped. + * Common second level post handler will be ret_from_syscall. + * Common (non-trace) exit point to that is syscall_ret (saving + * result to r2). Common bad exit point is syscall_bad (returning + * ENOSYS then saved to r2). + * + */ + +unknown_trap: + /* Unknown Trap or User Trace */ + movi do_unknown_trapa, r6 + ptabs r6, tr0 + ld.q r3, FRAME_R(9), r2 /* r2 = #arg << 16 | syscall # */ + andi r2, 0x1ff, r2 /* r2 = syscall # */ + blink tr0, LINK + + pta syscall_ret, tr0 + blink tr0, ZERO + + /* New syscall implementation*/ +system_call: + pta unknown_trap, tr0 + or r5, ZERO, r4 /* TRA (=r5) -> r4 */ + shlri r4, 20, r4 + bnei r4, 1, tr0 /* unknown_trap if not 0x1yzzzz */ + + /* It's a system call */ + st.q r3, FRAME_S(FSYSCALL_ID), r5 /* ID (0x1yzzzz) -> stack */ + andi r5, 0x1ff, r5 /* syscall # -> r5 */ + + STI() + + pta syscall_allowed, tr0 + movi NR_syscalls - 1, r4 /* Last valid */ + bgeu/l r4, r5, tr0 + +syscall_bad: + /* Return ENOSYS ! */ + movi -(ENOSYS), r2 /* Fall-through */ + + .global syscall_ret +syscall_ret: + st.q SP, FRAME_R(9), r2 /* Expecting SP back to BASIC frame */ + +#ifdef CONFIG_POOR_MANS_STRACE + /* nothing useful in registers at this point */ + + movi evt_debug2, r5 + ori r5, 1, r5 + ptabs r5, tr0 + ld.q SP, FRAME_R(9), r2 + or SP, ZERO, r3 + blink tr0, LINK +#endif + + ld.q SP, FRAME_S(FSPC), r2 + addi r2, 4, r2 /* Move PC, being pre-execution event */ + st.q SP, FRAME_S(FSPC), r2 + pta ret_from_syscall, tr0 + blink tr0, ZERO + + +/* A different return path for ret_from_fork, because we now need + * to call schedule_tail with the later kernels. Because prev is + * loaded into r2 by switch_to() means we can just call it straight away + */ + +.global ret_from_fork +ret_from_fork: + + movi schedule_tail,r5 + ori r5, 1, r5 + ptabs r5, tr0 + blink tr0, LINK + +#ifdef CONFIG_POOR_MANS_STRACE + /* nothing useful in registers at this point */ + + movi evt_debug2, r5 + ori r5, 1, r5 + ptabs r5, tr0 + ld.q SP, FRAME_R(9), r2 + or SP, ZERO, r3 + blink tr0, LINK +#endif + + ld.q SP, FRAME_S(FSPC), r2 + addi r2, 4, r2 /* Move PC, being pre-execution event */ + st.q SP, FRAME_S(FSPC), r2 + pta ret_from_syscall, tr0 + blink tr0, ZERO + + + +syscall_allowed: + /* Use LINK to deflect the exit point, default is syscall_ret */ + pta syscall_ret, tr0 + gettr tr0, LINK + pta syscall_notrace, tr0 + + getcon KCR0, r2 + ld.l r2, TI_FLAGS, r4 + movi (1 << TIF_SYSCALL_TRACE), r6 + and r6, r4, r6 + beq/l r6, ZERO, tr0 + + /* Trace it by calling syscall_trace before and after */ + movi syscall_trace, r4 + ptabs r4, tr0 + blink tr0, LINK + /* Reload syscall number as r5 is trashed by syscall_trace */ + ld.q SP, FRAME_S(FSYSCALL_ID), r5 + andi r5, 0x1ff, r5 + + pta syscall_ret_trace, tr0 + gettr tr0, LINK + +syscall_notrace: + /* Now point to the appropriate 4th level syscall handler */ + movi sys_call_table, r4 + shlli r5, 2, r5 + ldx.l r4, r5, r5 + ptabs r5, tr0 + + /* Prepare original args */ + ld.q SP, FRAME_R(2), r2 + ld.q SP, FRAME_R(3), r3 + ld.q SP, FRAME_R(4), r4 + ld.q SP, FRAME_R(5), r5 + ld.q SP, FRAME_R(6), r6 + ld.q SP, FRAME_R(7), r7 + + /* And now the trick for those syscalls requiring regs * ! */ + or SP, ZERO, r8 + + /* Call it */ + blink tr0, ZERO /* LINK is already properly set */ + +syscall_ret_trace: + /* We get back here only if under trace */ + st.q SP, FRAME_R(9), r2 /* Save return value */ + + movi syscall_trace, LINK + ptabs LINK, tr0 + blink tr0, LINK + + /* This needs to be done after any syscall tracing */ + ld.q SP, FRAME_S(FSPC), r2 + addi r2, 4, r2 /* Move PC, being pre-execution event */ + st.q SP, FRAME_S(FSPC), r2 + + pta ret_from_syscall, tr0 + blink tr0, ZERO /* Resume normal return sequence */ + +/* + * --- Switch to running under a particular ASID and return the previous ASID value + * --- The caller is assumed to have done a cli before calling this. + * + * Input r2 : new ASID + * Output r2 : old ASID + */ + + .global switch_and_save_asid +switch_and_save_asid: + getcon sr, r0 + movi 255, r4 + shlli r4, 16, r4 /* r4 = mask to select ASID */ + and r0, r4, r3 /* r3 = shifted old ASID */ + andi r2, 255, r2 /* mask down new ASID */ + shlli r2, 16, r2 /* align new ASID against SR.ASID */ + andc r0, r4, r0 /* efface old ASID from SR */ + or r0, r2, r0 /* insert the new ASID */ + putcon r0, ssr + movi 1f, r0 + putcon r0, spc + rte + nop +1: + ptabs LINK, tr0 + shlri r3, 16, r2 /* r2 = old ASID */ + blink tr0, r63 + + .global route_to_panic_handler +route_to_panic_handler: + /* Switch to real mode, goto panic_handler, don't return. Useful for + last-chance debugging, e.g. if no output wants to go to the console. + */ + + movi panic_handler - CONFIG_CACHED_MEMORY_OFFSET, r1 + ptabs r1, tr0 + pta 1f, tr1 + gettr tr1, r0 + putcon r0, spc + getcon sr, r0 + movi 1, r1 + shlli r1, 31, r1 + andc r0, r1, r0 + putcon r0, ssr + rte + nop +1: /* Now in real mode */ + blink tr0, r63 + nop + + .global peek_real_address_q +peek_real_address_q: + /* Two args: + r2 : real mode address to peek + r2(out) : result quadword + + This is provided as a cheapskate way of manipulating device + registers for debugging (to avoid the need to onchip_remap the debug + module, and to avoid the need to onchip_remap the watchpoint + controller in a way that identity maps sufficient bits to avoid the + SH5-101 cut2 silicon defect). + + This code is not performance critical + */ + + add.l r2, r63, r2 /* sign extend address */ + getcon sr, r0 /* r0 = saved original SR */ + movi 1, r1 + shlli r1, 28, r1 + or r0, r1, r1 /* r0 with block bit set */ + putcon r1, sr /* now in critical section */ + movi 1, r36 + shlli r36, 31, r36 + andc r1, r36, r1 /* turn sr.mmu off in real mode section */ + + putcon r1, ssr + movi .peek0 - CONFIG_CACHED_MEMORY_OFFSET, r36 /* real mode target address */ + movi 1f, r37 /* virtual mode return addr */ + putcon r36, spc + + synco + rte + nop + +.peek0: /* come here in real mode, don't touch caches!! + still in critical section (sr.bl==1) */ + putcon r0, ssr + putcon r37, spc + /* Here's the actual peek. If the address is bad, all bets are now off + * what will happen (handlers invoked in real-mode = bad news) */ + ld.q r2, 0, r2 + synco + rte /* Back to virtual mode */ + nop + +1: + ptabs LINK, tr0 + blink tr0, r63 + + .global poke_real_address_q +poke_real_address_q: + /* Two args: + r2 : real mode address to poke + r3 : quadword value to write. + + This is provided as a cheapskate way of manipulating device + registers for debugging (to avoid the need to onchip_remap the debug + module, and to avoid the need to onchip_remap the watchpoint + controller in a way that identity maps sufficient bits to avoid the + SH5-101 cut2 silicon defect). + + This code is not performance critical + */ + + add.l r2, r63, r2 /* sign extend address */ + getcon sr, r0 /* r0 = saved original SR */ + movi 1, r1 + shlli r1, 28, r1 + or r0, r1, r1 /* r0 with block bit set */ + putcon r1, sr /* now in critical section */ + movi 1, r36 + shlli r36, 31, r36 + andc r1, r36, r1 /* turn sr.mmu off in real mode section */ + + putcon r1, ssr + movi .poke0-CONFIG_CACHED_MEMORY_OFFSET, r36 /* real mode target address */ + movi 1f, r37 /* virtual mode return addr */ + putcon r36, spc + + synco + rte + nop + +.poke0: /* come here in real mode, don't touch caches!! + still in critical section (sr.bl==1) */ + putcon r0, ssr + putcon r37, spc + /* Here's the actual poke. If the address is bad, all bets are now off + * what will happen (handlers invoked in real-mode = bad news) */ + st.q r2, 0, r3 + synco + rte /* Back to virtual mode */ + nop + +1: + ptabs LINK, tr0 + blink tr0, r63 + +/* + * --- User Access Handling Section + */ + +/* + * User Access support. It all moved to non inlined Assembler + * functions in here. + * + * __kernel_size_t __copy_user(void *__to, const void *__from, + * __kernel_size_t __n) + * + * Inputs: + * (r2) target address + * (r3) source address + * (r4) size in bytes + * + * Ouputs: + * (*r2) target data + * (r2) non-copied bytes + * + * If a fault occurs on the user pointer, bail out early and return the + * number of bytes not copied in r2. + * Strategy : for large blocks, call a real memcpy function which can + * move >1 byte at a time using unaligned ld/st instructions, and can + * manipulate the cache using prefetch + alloco to improve the speed + * further. If a fault occurs in that function, just revert to the + * byte-by-byte approach used for small blocks; this is rare so the + * performance hit for that case does not matter. + * + * For small blocks it's not worth the overhead of setting up and calling + * the memcpy routine; do the copy a byte at a time. + * + */ + .global __copy_user +__copy_user: + pta __copy_user_byte_by_byte, tr1 + movi 16, r0 ! this value is a best guess, should tune it by benchmarking + bge/u r0, r4, tr1 + pta copy_user_memcpy, tr0 + addi SP, -32, SP + /* Save arguments in case we have to fix-up unhandled page fault */ + st.q SP, 0, r2 + st.q SP, 8, r3 + st.q SP, 16, r4 + st.q SP, 24, r35 ! r35 is callee-save + /* Save LINK in a register to reduce RTS time later (otherwise + ld SP,*,LINK;ptabs LINK;trn;blink trn,r63 becomes a critical path) */ + ori LINK, 0, r35 + blink tr0, LINK + + /* Copy completed normally if we get back here */ + ptabs r35, tr0 + ld.q SP, 24, r35 + /* don't restore r2-r4, pointless */ + /* set result=r2 to zero as the copy must have succeeded. */ + or r63, r63, r2 + addi SP, 32, SP + blink tr0, r63 ! RTS + + .global __copy_user_fixup +__copy_user_fixup: + /* Restore stack frame */ + ori r35, 0, LINK + ld.q SP, 24, r35 + ld.q SP, 16, r4 + ld.q SP, 8, r3 + ld.q SP, 0, r2 + addi SP, 32, SP + /* Fall through to original code, in the 'same' state we entered with */ + +/* The slow byte-by-byte method is used if the fast copy traps due to a bad + user address. In that rare case, the speed drop can be tolerated. */ +__copy_user_byte_by_byte: + pta ___copy_user_exit, tr1 + pta ___copy_user1, tr0 + beq/u r4, r63, tr1 /* early exit for zero length copy */ + sub r2, r3, r0 + addi r0, -1, r0 + +___copy_user1: + ld.b r3, 0, r5 /* Fault address 1 */ + + /* Could rewrite this to use just 1 add, but the second comes 'free' + due to load latency */ + addi r3, 1, r3 + addi r4, -1, r4 /* No real fixup required */ +___copy_user2: + stx.b r3, r0, r5 /* Fault address 2 */ + bne r4, ZERO, tr0 + +___copy_user_exit: + or r4, ZERO, r2 + ptabs LINK, tr0 + blink tr0, ZERO + +/* + * __kernel_size_t __clear_user(void *addr, __kernel_size_t size) + * + * Inputs: + * (r2) target address + * (r3) size in bytes + * + * Ouputs: + * (*r2) zero-ed target data + * (r2) non-zero-ed bytes + */ + .global __clear_user +__clear_user: + pta ___clear_user_exit, tr1 + pta ___clear_user1, tr0 + beq/u r3, r63, tr1 + +___clear_user1: + st.b r2, 0, ZERO /* Fault address */ + addi r2, 1, r2 + addi r3, -1, r3 /* No real fixup required */ + bne r3, ZERO, tr0 + +___clear_user_exit: + or r3, ZERO, r2 + ptabs LINK, tr0 + blink tr0, ZERO + + +/* + * int __strncpy_from_user(unsigned long __dest, unsigned long __src, + * int __count) + * + * Inputs: + * (r2) target address + * (r3) source address + * (r4) maximum size in bytes + * + * Ouputs: + * (*r2) copied data + * (r2) -EFAULT (in case of faulting) + * copied data (otherwise) + */ + .global __strncpy_from_user +__strncpy_from_user: + pta ___strncpy_from_user1, tr0 + pta ___strncpy_from_user_done, tr1 + or r4, ZERO, r5 /* r5 = original count */ + beq/u r4, r63, tr1 /* early exit if r4==0 */ + movi -(EFAULT), r6 /* r6 = reply, no real fixup */ + or ZERO, ZERO, r7 /* r7 = data, clear top byte of data */ + +___strncpy_from_user1: + ld.b r3, 0, r7 /* Fault address: only in reading */ + st.b r2, 0, r7 + addi r2, 1, r2 + addi r3, 1, r3 + beq/u ZERO, r7, tr1 + addi r4, -1, r4 /* return real number of copied bytes */ + bne/l ZERO, r4, tr0 + +___strncpy_from_user_done: + sub r5, r4, r6 /* If done, return copied */ + +___strncpy_from_user_exit: + or r6, ZERO, r2 + ptabs LINK, tr0 + blink tr0, ZERO + +/* + * extern long __strnlen_user(const char *__s, long __n) + * + * Inputs: + * (r2) source address + * (r3) source size in bytes + * + * Ouputs: + * (r2) -EFAULT (in case of faulting) + * string length (otherwise) + */ + .global __strnlen_user +__strnlen_user: + pta ___strnlen_user_set_reply, tr0 + pta ___strnlen_user1, tr1 + or ZERO, ZERO, r5 /* r5 = counter */ + movi -(EFAULT), r6 /* r6 = reply, no real fixup */ + or ZERO, ZERO, r7 /* r7 = data, clear top byte of data */ + beq r3, ZERO, tr0 + +___strnlen_user1: + ldx.b r2, r5, r7 /* Fault address: only in reading */ + addi r3, -1, r3 /* No real fixup */ + addi r5, 1, r5 + beq r3, ZERO, tr0 + bne r7, ZERO, tr1 +! The line below used to be active. This meant led to a junk byte lying between each pair +! of entries in the argv & envp structures in memory. Whilst the program saw the right data +! via the argv and envp arguments to main, it meant the 'flat' representation visible through +! /proc/$pid/cmdline was corrupt, causing trouble with ps, for example. +! addi r5, 1, r5 /* Include '\0' */ + +___strnlen_user_set_reply: + or r5, ZERO, r6 /* If done, return counter */ + +___strnlen_user_exit: + or r6, ZERO, r2 + ptabs LINK, tr0 + blink tr0, ZERO + +/* + * extern long __get_user_asm_?(void *val, long addr) + * + * Inputs: + * (r2) dest address + * (r3) source address (in User Space) + * + * Ouputs: + * (r2) -EFAULT (faulting) + * 0 (not faulting) + */ + .global __get_user_asm_b +__get_user_asm_b: + or r2, ZERO, r4 + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___get_user_asm_b1: + ld.b r3, 0, r5 /* r5 = data */ + st.b r4, 0, r5 + or ZERO, ZERO, r2 + +___get_user_asm_b_exit: + ptabs LINK, tr0 + blink tr0, ZERO + + + .global __get_user_asm_w +__get_user_asm_w: + or r2, ZERO, r4 + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___get_user_asm_w1: + ld.w r3, 0, r5 /* r5 = data */ + st.w r4, 0, r5 + or ZERO, ZERO, r2 + +___get_user_asm_w_exit: + ptabs LINK, tr0 + blink tr0, ZERO + + + .global __get_user_asm_l +__get_user_asm_l: + or r2, ZERO, r4 + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___get_user_asm_l1: + ld.l r3, 0, r5 /* r5 = data */ + st.l r4, 0, r5 + or ZERO, ZERO, r2 + +___get_user_asm_l_exit: + ptabs LINK, tr0 + blink tr0, ZERO + + + .global __get_user_asm_q +__get_user_asm_q: + or r2, ZERO, r4 + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___get_user_asm_q1: + ld.q r3, 0, r5 /* r5 = data */ + st.q r4, 0, r5 + or ZERO, ZERO, r2 + +___get_user_asm_q_exit: + ptabs LINK, tr0 + blink tr0, ZERO + +/* + * extern long __put_user_asm_?(void *pval, long addr) + * + * Inputs: + * (r2) kernel pointer to value + * (r3) dest address (in User Space) + * + * Ouputs: + * (r2) -EFAULT (faulting) + * 0 (not faulting) + */ + .global __put_user_asm_b +__put_user_asm_b: + ld.b r2, 0, r4 /* r4 = data */ + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___put_user_asm_b1: + st.b r3, 0, r4 + or ZERO, ZERO, r2 + +___put_user_asm_b_exit: + ptabs LINK, tr0 + blink tr0, ZERO + + + .global __put_user_asm_w +__put_user_asm_w: + ld.w r2, 0, r4 /* r4 = data */ + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___put_user_asm_w1: + st.w r3, 0, r4 + or ZERO, ZERO, r2 + +___put_user_asm_w_exit: + ptabs LINK, tr0 + blink tr0, ZERO + + + .global __put_user_asm_l +__put_user_asm_l: + ld.l r2, 0, r4 /* r4 = data */ + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___put_user_asm_l1: + st.l r3, 0, r4 + or ZERO, ZERO, r2 + +___put_user_asm_l_exit: + ptabs LINK, tr0 + blink tr0, ZERO + + + .global __put_user_asm_q +__put_user_asm_q: + ld.q r2, 0, r4 /* r4 = data */ + movi -(EFAULT), r2 /* r2 = reply, no real fixup */ + +___put_user_asm_q1: + st.q r3, 0, r4 + or ZERO, ZERO, r2 + +___put_user_asm_q_exit: + ptabs LINK, tr0 + blink tr0, ZERO + +panic_stash_regs: + /* The idea is : when we get an unhandled panic, we dump the registers + to a known memory location, the just sit in a tight loop. + This allows the human to look at the memory region through the GDB + session (assuming the debug module's SHwy initiator isn't locked up + or anything), to hopefully analyze the cause of the panic. */ + + /* On entry, former r15 (SP) is in DCR + former r0 is at resvec_saved_area + 0 + former r1 is at resvec_saved_area + 8 + former tr0 is at resvec_saved_area + 32 + DCR is the only register whose value is lost altogether. + */ + + movi 0xffffffff80000000, r0 ! phy of dump area + ld.q SP, 0x000, r1 ! former r0 + st.q r0, 0x000, r1 + ld.q SP, 0x008, r1 ! former r1 + st.q r0, 0x008, r1 + st.q r0, 0x010, r2 + st.q r0, 0x018, r3 + st.q r0, 0x020, r4 + st.q r0, 0x028, r5 + st.q r0, 0x030, r6 + st.q r0, 0x038, r7 + st.q r0, 0x040, r8 + st.q r0, 0x048, r9 + st.q r0, 0x050, r10 + st.q r0, 0x058, r11 + st.q r0, 0x060, r12 + st.q r0, 0x068, r13 + st.q r0, 0x070, r14 + getcon dcr, r14 + st.q r0, 0x078, r14 + st.q r0, 0x080, r16 + st.q r0, 0x088, r17 + st.q r0, 0x090, r18 + st.q r0, 0x098, r19 + st.q r0, 0x0a0, r20 + st.q r0, 0x0a8, r21 + st.q r0, 0x0b0, r22 + st.q r0, 0x0b8, r23 + st.q r0, 0x0c0, r24 + st.q r0, 0x0c8, r25 + st.q r0, 0x0d0, r26 + st.q r0, 0x0d8, r27 + st.q r0, 0x0e0, r28 + st.q r0, 0x0e8, r29 + st.q r0, 0x0f0, r30 + st.q r0, 0x0f8, r31 + st.q r0, 0x100, r32 + st.q r0, 0x108, r33 + st.q r0, 0x110, r34 + st.q r0, 0x118, r35 + st.q r0, 0x120, r36 + st.q r0, 0x128, r37 + st.q r0, 0x130, r38 + st.q r0, 0x138, r39 + st.q r0, 0x140, r40 + st.q r0, 0x148, r41 + st.q r0, 0x150, r42 + st.q r0, 0x158, r43 + st.q r0, 0x160, r44 + st.q r0, 0x168, r45 + st.q r0, 0x170, r46 + st.q r0, 0x178, r47 + st.q r0, 0x180, r48 + st.q r0, 0x188, r49 + st.q r0, 0x190, r50 + st.q r0, 0x198, r51 + st.q r0, 0x1a0, r52 + st.q r0, 0x1a8, r53 + st.q r0, 0x1b0, r54 + st.q r0, 0x1b8, r55 + st.q r0, 0x1c0, r56 + st.q r0, 0x1c8, r57 + st.q r0, 0x1d0, r58 + st.q r0, 0x1d8, r59 + st.q r0, 0x1e0, r60 + st.q r0, 0x1e8, r61 + st.q r0, 0x1f0, r62 + st.q r0, 0x1f8, r63 ! bogus, but for consistency's sake... + + ld.q SP, 0x020, r1 ! former tr0 + st.q r0, 0x200, r1 + gettr tr1, r1 + st.q r0, 0x208, r1 + gettr tr2, r1 + st.q r0, 0x210, r1 + gettr tr3, r1 + st.q r0, 0x218, r1 + gettr tr4, r1 + st.q r0, 0x220, r1 + gettr tr5, r1 + st.q r0, 0x228, r1 + gettr tr6, r1 + st.q r0, 0x230, r1 + gettr tr7, r1 + st.q r0, 0x238, r1 + + getcon sr, r1 + getcon ssr, r2 + getcon pssr, r3 + getcon spc, r4 + getcon pspc, r5 + getcon intevt, r6 + getcon expevt, r7 + getcon pexpevt, r8 + getcon tra, r9 + getcon tea, r10 + getcon kcr0, r11 + getcon kcr1, r12 + getcon vbr, r13 + getcon resvec, r14 + + st.q r0, 0x240, r1 + st.q r0, 0x248, r2 + st.q r0, 0x250, r3 + st.q r0, 0x258, r4 + st.q r0, 0x260, r5 + st.q r0, 0x268, r6 + st.q r0, 0x270, r7 + st.q r0, 0x278, r8 + st.q r0, 0x280, r9 + st.q r0, 0x288, r10 + st.q r0, 0x290, r11 + st.q r0, 0x298, r12 + st.q r0, 0x2a0, r13 + st.q r0, 0x2a8, r14 + + getcon SPC,r2 + getcon SSR,r3 + getcon EXPEVT,r4 + /* Prepare to jump to C - physical address */ + movi panic_handler-CONFIG_CACHED_MEMORY_OFFSET, r1 + ori r1, 1, r1 + ptabs r1, tr0 + getcon DCR, SP + blink tr0, ZERO + nop + nop + nop + nop + + + + +/* + * --- Signal Handling Section + */ + +/* + * extern long long _sa_default_rt_restorer + * extern long long _sa_default_restorer + * + * or, better, + * + * extern void _sa_default_rt_restorer(void) + * extern void _sa_default_restorer(void) + * + * Code prototypes to do a sys_rt_sigreturn() or sys_sysreturn() + * from user space. Copied into user space by signal management. + * Both must be quad aligned and 2 quad long (4 instructions). + * + */ + .balign 8 + .global sa_default_rt_restorer +sa_default_rt_restorer: + movi 0x10, r9 + shori __NR_rt_sigreturn, r9 + trapa r9 + nop + + .balign 8 + .global sa_default_restorer +sa_default_restorer: + movi 0x10, r9 + shori __NR_sigreturn, r9 + trapa r9 + nop + +/* + * --- __ex_table Section + */ + +/* + * User Access Exception Table. + */ + .section __ex_table, "a" + + .global asm_uaccess_start /* Just a marker */ +asm_uaccess_start: + + .long ___copy_user1, ___copy_user_exit + .long ___copy_user2, ___copy_user_exit + .long ___clear_user1, ___clear_user_exit + .long ___strncpy_from_user1, ___strncpy_from_user_exit + .long ___strnlen_user1, ___strnlen_user_exit + .long ___get_user_asm_b1, ___get_user_asm_b_exit + .long ___get_user_asm_w1, ___get_user_asm_w_exit + .long ___get_user_asm_l1, ___get_user_asm_l_exit + .long ___get_user_asm_q1, ___get_user_asm_q_exit + .long ___put_user_asm_b1, ___put_user_asm_b_exit + .long ___put_user_asm_w1, ___put_user_asm_w_exit + .long ___put_user_asm_l1, ___put_user_asm_l_exit + .long ___put_user_asm_q1, ___put_user_asm_q_exit + + .global asm_uaccess_end /* Just a marker */ +asm_uaccess_end: + + + + +/* + * --- .text.init Section + */ + + .section .text.init, "ax" + +/* + * void trap_init (void) + * + */ + .global trap_init +trap_init: + addi SP, -24, SP /* Room to save r28/r29/r30 */ + st.q SP, 0, r28 + st.q SP, 8, r29 + st.q SP, 16, r30 + + /* Set VBR and RESVEC */ + movi LVBR_block, r19 + andi r19, -4, r19 /* reset MMUOFF + reserved */ + /* For RESVEC exceptions we force the MMU off, which means we need the + physical address. */ + movi LRESVEC_block-CONFIG_CACHED_MEMORY_OFFSET, r20 + andi r20, -4, r20 /* reset reserved */ + ori r20, 1, r20 /* set MMUOFF */ + putcon r19, VBR + putcon r20, RESVEC + + /* Sanity check */ + movi LVBR_block_end, r21 + andi r21, -4, r21 + movi BLOCK_SIZE, r29 /* r29 = expected size */ + or r19, ZERO, r30 + add r19, r29, r19 + + /* + * Ugly, but better loop forever now than crash afterwards. + * We should print a message, but if we touch LVBR or + * LRESVEC blocks we should not be surprised if we get stuck + * in trap_init(). + */ + pta trap_init_loop, tr1 + gettr tr1, r28 /* r28 = trap_init_loop */ + sub r21, r30, r30 /* r30 = actual size */ + + /* + * VBR/RESVEC handlers overlap by being bigger than + * allowed. Very bad. Just loop forever. + * (r28) panic/loop address + * (r29) expected size + * (r30) actual size + */ +trap_init_loop: + bne r19, r21, tr1 + + /* Now that exception vectors are set up reset SR.BL */ + getcon SR, r22 + movi SR_UNBLOCK_EXC, r23 + and r22, r23, r22 + putcon r22, SR + + addi SP, 24, SP + ptabs LINK, tr0 + blink tr0, ZERO + diff --git a/arch/sh64/kernel/fpu.c b/arch/sh64/kernel/fpu.c new file mode 100644 index 00000000000..8ad4ed6a6c9 --- /dev/null +++ b/arch/sh64/kernel/fpu.c @@ -0,0 +1,170 @@ +/* + * 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. + * + * arch/sh64/kernel/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 + * + */ + +#include <linux/sched.h> +#include <linux/signal.h> +#include <asm/processor.h> +#include <asm/user.h> +#include <asm/io.h> + +/* + * Initially load the FPU with signalling NANS. This bit pattern + * has the property that no matter whether considered as single or as + * double precision, it still represents a signalling NAN. + */ +#define sNAN64 0xFFFFFFFFFFFFFFFFULL +#define sNAN32 0xFFFFFFFFUL + +static union sh_fpu_union init_fpuregs = { + .hard = { + .fp_regs = { [0 ... 63] = sNAN32 }, + .fpscr = FPSCR_INIT + } +}; + +inline void fpsave(struct sh_fpu_hard_struct *fpregs) +{ + 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" (fpregs) + : "memory"); +} + + +static inline void +fpload(struct sh_fpu_hard_struct *fpregs) +{ + 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" (fpregs) ); +} + +void fpinit(struct sh_fpu_hard_struct *fpregs) +{ + *fpregs = init_fpuregs.hard; +} + +asmlinkage void +do_fpu_error(unsigned long ex, struct pt_regs *regs) +{ + struct task_struct *tsk = current; + + regs->pc += 4; + + tsk->thread.trap_no = 11; + tsk->thread.error_code = 0; + force_sig(SIGFPE, tsk); +} + + +asmlinkage void +do_fpu_state_restore(unsigned long ex, struct pt_regs *regs) +{ + void die(const char *str, struct pt_regs *regs, long err); + + if (! user_mode(regs)) + die("FPU used in kernel", regs, ex); + + regs->sr &= ~SR_FD; + + if (last_task_used_math == current) + return; + + grab_fpu(); + if (last_task_used_math != NULL) { + /* Other processes fpu state, save away */ + fpsave(&last_task_used_math->thread.fpu.hard); + } + last_task_used_math = current; + if (used_math()) { + fpload(¤t->thread.fpu.hard); + } else { + /* First time FPU user. */ + fpload(&init_fpuregs.hard); + set_used_math(); + } + release_fpu(); +} + diff --git a/arch/sh64/kernel/head.S b/arch/sh64/kernel/head.S new file mode 100644 index 00000000000..cc0b628a9ea --- /dev/null +++ b/arch/sh64/kernel/head.S @@ -0,0 +1,373 @@ +/* + * 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. + * + * arch/sh64/kernel/head.S + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003, 2004 Paul Mundt + * + * + * benedict.gaster@superh.com: 2nd May 2002 + * Moved definition of empty_zero_page to its own section allowing + * it to be placed at an absolute address known at load time. + * + * lethal@linux-sh.org: 9th May 2003 + * Kill off GLOBAL_NAME() usage. + * + * lethal@linux-sh.org: 8th May 2004 + * Add early SCIF console DTLB mapping. + */ + +#include <linux/config.h> + +#include <asm/page.h> +#include <asm/mmu_context.h> +#include <asm/cache.h> +#include <asm/tlb.h> +#include <asm/processor.h> +#include <asm/registers.h> +#include <asm/thread_info.h> + +/* + * MMU defines: TLB boundaries. + */ + +#define MMUIR_FIRST ITLB_FIXED +#define MMUIR_END ITLB_LAST_VAR_UNRESTRICTED+TLB_STEP +#define MMUIR_STEP TLB_STEP + +#define MMUDR_FIRST DTLB_FIXED +#define MMUDR_END DTLB_LAST_VAR_UNRESTRICTED+TLB_STEP +#define MMUDR_STEP TLB_STEP + +/* Safety check : CONFIG_CACHED_MEMORY_OFFSET has to be a multiple of 512Mb */ +#if (CONFIG_CACHED_MEMORY_OFFSET & ((1UL<<29)-1)) +#error "CONFIG_CACHED_MEMORY_OFFSET must be a multiple of 512Mb" +#endif + +/* + * MMU defines: Fixed TLBs. + */ +/* Deal safely with the case where the base of RAM is not 512Mb aligned */ + +#define ALIGN_512M_MASK (0xffffffffe0000000) +#define ALIGNED_EFFECTIVE ((CONFIG_CACHED_MEMORY_OFFSET + CONFIG_MEMORY_START) & ALIGN_512M_MASK) +#define ALIGNED_PHYSICAL (CONFIG_MEMORY_START & ALIGN_512M_MASK) + +#define MMUIR_TEXT_H (0x0000000000000003 | ALIGNED_EFFECTIVE) + /* Enabled, Shared, ASID 0, Eff. Add. 0xA0000000 */ + +#define MMUIR_TEXT_L (0x000000000000009a | ALIGNED_PHYSICAL) + /* 512 Mb, Cacheable, Write-back, execute, Not User, Ph. Add. */ + +#define MMUDR_CACHED_H 0x0000000000000003 | ALIGNED_EFFECTIVE + /* Enabled, Shared, ASID 0, Eff. Add. 0xA0000000 */ +#define MMUDR_CACHED_L 0x000000000000015a | ALIGNED_PHYSICAL + /* 512 Mb, Cacheable, Write-back, read/write, Not User, Ph. Add. */ + +#ifdef CONFIG_ICACHE_DISABLED +#define ICCR0_INIT_VAL ICCR0_OFF /* ICACHE off */ +#else +#define ICCR0_INIT_VAL ICCR0_ON | ICCR0_ICI /* ICE + ICI */ +#endif +#define ICCR1_INIT_VAL ICCR1_NOLOCK /* No locking */ + +#if defined (CONFIG_DCACHE_DISABLED) +#define OCCR0_INIT_VAL OCCR0_OFF /* D-cache: off */ +#elif defined (CONFIG_DCACHE_WRITE_THROUGH) +#define OCCR0_INIT_VAL OCCR0_ON | OCCR0_OCI | OCCR0_WT /* D-cache: on, */ + /* WT, invalidate */ +#elif defined (CONFIG_DCACHE_WRITE_BACK) +#define OCCR0_INIT_VAL OCCR0_ON | OCCR0_OCI | OCCR0_WB /* D-cache: on, */ + /* WB, invalidate */ +#else +#error preprocessor flag CONFIG_DCACHE_... not recognized! +#endif + +#define OCCR1_INIT_VAL OCCR1_NOLOCK /* No locking */ + + .section .empty_zero_page, "aw" + .global empty_zero_page + +empty_zero_page: + .long 1 /* MOUNT_ROOT_RDONLY */ + .long 0 /* RAMDISK_FLAGS */ + .long 0x0200 /* ORIG_ROOT_DEV */ + .long 1 /* LOADER_TYPE */ + .long 0x00800000 /* INITRD_START */ + .long 0x00800000 /* INITRD_SIZE */ + .long 0 + + .text + .balign 4096,0,4096 + + .section .data, "aw" + .balign PAGE_SIZE + + .section .data, "aw" + .balign PAGE_SIZE + + .global swapper_pg_dir +swapper_pg_dir: + .space PAGE_SIZE, 0 + + .global empty_bad_page +empty_bad_page: + .space PAGE_SIZE, 0 + + .global empty_bad_pte_table +empty_bad_pte_table: + .space PAGE_SIZE, 0 + + .global fpu_in_use +fpu_in_use: .quad 0 + + + .section .text, "ax" + .balign L1_CACHE_BYTES +/* + * Condition at the entry of __stext: + * . Reset state: + * . SR.FD = 1 (FPU disabled) + * . SR.BL = 1 (Exceptions disabled) + * . SR.MD = 1 (Privileged Mode) + * . SR.MMU = 0 (MMU Disabled) + * . SR.CD = 0 (CTC User Visible) + * . SR.IMASK = Undefined (Interrupt Mask) + * + * Operations supposed to be performed by __stext: + * . prevent speculative fetch onto device memory while MMU is off + * . reflect as much as possible SH5 ABI (r15, r26, r27, r18) + * . first, save CPU state and set it to something harmless + * . any CPU detection and/or endianness settings (?) + * . initialize EMI/LMI (but not TMU/RTC/INTC/SCIF): TBD + * . set initial TLB entries for cached and uncached regions + * (no fine granularity paging) + * . set initial cache state + * . enable MMU and caches + * . set CPU to a consistent state + * . registers (including stack pointer and current/KCR0) + * . NOT expecting to set Exception handling nor VBR/RESVEC/DCR + * at this stage. This is all to later Linux initialization steps. + * . initialize FPU + * . clear BSS + * . jump into start_kernel() + * . be prepared to hopeless start_kernel() returns. + * + */ + .global _stext +_stext: + /* + * Prevent speculative fetch on device memory due to + * uninitialized target registers. + */ + ptabs/u ZERO, tr0 + ptabs/u ZERO, tr1 + ptabs/u ZERO, tr2 + ptabs/u ZERO, tr3 + ptabs/u ZERO, tr4 + ptabs/u ZERO, tr5 + ptabs/u ZERO, tr6 + ptabs/u ZERO, tr7 + synci + + /* + * Read/Set CPU state. After this block: + * r29 = Initial SR + */ + getcon SR, r29 + movi SR_HARMLESS, r20 + putcon r20, SR + + /* + * Initialize EMI/LMI. To Be Done. + */ + + /* + * CPU detection and/or endianness settings (?). To Be Done. + * Pure PIC code here, please ! Just save state into r30. + * After this block: + * r30 = CPU type/Platform Endianness + */ + + /* + * Set initial TLB entries for cached and uncached regions. + * Note: PTA/BLINK is PIC code, PTABS/BLINK isn't ! + */ + /* Clear ITLBs */ + pta clear_ITLB, tr1 + movi MMUIR_FIRST, r21 + movi MMUIR_END, r22 +clear_ITLB: + putcfg r21, 0, ZERO /* Clear MMUIR[n].PTEH.V */ + addi r21, MMUIR_STEP, r21 + bne r21, r22, tr1 + + /* Clear DTLBs */ + pta clear_DTLB, tr1 + movi MMUDR_FIRST, r21 + movi MMUDR_END, r22 +clear_DTLB: + putcfg r21, 0, ZERO /* Clear MMUDR[n].PTEH.V */ + addi r21, MMUDR_STEP, r21 + bne r21, r22, tr1 + + /* Map one big (512Mb) page for ITLB */ + movi MMUIR_FIRST, r21 + movi MMUIR_TEXT_L, r22 /* PTEL first */ + add.l r22, r63, r22 /* Sign extend */ + putcfg r21, 1, r22 /* Set MMUIR[0].PTEL */ + movi MMUIR_TEXT_H, r22 /* PTEH last */ + add.l r22, r63, r22 /* Sign extend */ + putcfg r21, 0, r22 /* Set MMUIR[0].PTEH */ + + /* Map one big CACHED (512Mb) page for DTLB */ + movi MMUDR_FIRST, r21 + movi MMUDR_CACHED_L, r22 /* PTEL first */ + add.l r22, r63, r22 /* Sign extend */ + putcfg r21, 1, r22 /* Set MMUDR[0].PTEL */ + movi MMUDR_CACHED_H, r22 /* PTEH last */ + add.l r22, r63, r22 /* Sign extend */ + putcfg r21, 0, r22 /* Set MMUDR[0].PTEH */ + +#ifdef CONFIG_EARLY_PRINTK + /* + * Setup a DTLB translation for SCIF phys. + */ + addi r21, MMUDR_STEP, r21 + movi 0x0a03, r22 /* SCIF phys */ + shori 0x0148, r22 + putcfg r21, 1, r22 /* PTEL first */ + movi 0xfa03, r22 /* 0xfa030000, fixed SCIF virt */ + shori 0x0003, r22 + putcfg r21, 0, r22 /* PTEH last */ +#endif + + /* + * Set cache behaviours. + */ + /* ICache */ + movi ICCR_BASE, r21 + movi ICCR0_INIT_VAL, r22 + movi ICCR1_INIT_VAL, r23 + putcfg r21, ICCR_REG0, r22 + putcfg r21, ICCR_REG1, r23 + + /* OCache */ + movi OCCR_BASE, r21 + movi OCCR0_INIT_VAL, r22 + movi OCCR1_INIT_VAL, r23 + putcfg r21, OCCR_REG0, r22 + putcfg r21, OCCR_REG1, r23 + + + /* + * Enable Caches and MMU. Do the first non-PIC jump. + * Now head.S global variables, constants and externs + * can be used. + */ + getcon SR, r21 + movi SR_ENABLE_MMU, r22 + or r21, r22, r21 + putcon r21, SSR + movi hyperspace, r22 + ori r22, 1, r22 /* Make it SHmedia, not required but..*/ + putcon r22, SPC + synco + rte /* And now go into the hyperspace ... */ +hyperspace: /* ... that's the next instruction ! */ + + /* + * Set CPU to a consistent state. + * r31 = FPU support flag + * tr0/tr7 in use. Others give a chance to loop somewhere safe + */ + movi start_kernel, r32 + ori r32, 1, r32 + + ptabs r32, tr0 /* r32 = _start_kernel address */ + pta/u hopeless, tr1 + pta/u hopeless, tr2 + pta/u hopeless, tr3 + pta/u hopeless, tr4 + pta/u hopeless, tr5 + pta/u hopeless, tr6 + pta/u hopeless, tr7 + gettr tr1, r28 /* r28 = hopeless address */ + + /* Set initial stack pointer */ + movi init_thread_union, SP + putcon SP, KCR0 /* Set current to init_task */ + movi THREAD_SIZE, r22 /* Point to the end */ + add SP, r22, SP + + /* + * Initialize FPU. + * Keep FPU flag in r31. After this block: + * r31 = FPU flag + */ + movi fpu_in_use, r31 /* Temporary */ + +#ifdef CONFIG_SH_FPU + getcon SR, r21 + movi SR_ENABLE_FPU, r22 + and r21, r22, r22 + putcon r22, SR /* Try to enable */ + getcon SR, r22 + xor r21, r22, r21 + shlri r21, 15, r21 /* Supposedly 0/1 */ + st.q r31, 0 , r21 /* Set fpu_in_use */ +#else + movi 0, r21 + st.q r31, 0 , r21 /* Set fpu_in_use */ +#endif + or r21, ZERO, r31 /* Set FPU flag at last */ + +#ifndef CONFIG_SH_NO_BSS_INIT +/* Don't clear BSS if running on slow platforms such as an RTL simulation, + remote memory via SHdebug link, etc. For these the memory can be guaranteed + to be all zero on boot anyway. */ + /* + * Clear bss + */ + pta clear_quad, tr1 + movi __bss_start, r22 + movi _end, r23 +clear_quad: + st.q r22, 0, ZERO + addi r22, 8, r22 + bne r22, r23, tr1 /* Both quad aligned, see vmlinux.lds.S */ +#endif + pta/u hopeless, tr1 + + /* Say bye to head.S but be prepared to wrongly get back ... */ + blink tr0, LINK + + /* If we ever get back here through LINK/tr1-tr7 */ + pta/u hopeless, tr7 + +hopeless: + /* + * Something's badly wrong here. Loop endlessly, + * there's nothing more we can do about it. + * + * Note on hopeless: it can be jumped into invariably + * before or after jumping into hyperspace. The only + * requirement is to be PIC called (PTA) before and + * any way (PTA/PTABS) after. According to Virtual + * to Physical mapping a simulator/emulator can easily + * tell where we came here from just looking at hopeless + * (PC) address. + * + * For debugging purposes: + * (r28) hopeless/loop address + * (r29) Original SR + * (r30) CPU type/Platform endianness + * (r31) FPU Support + * (r32) _start_kernel address + */ + blink tr7, ZERO + + diff --git a/arch/sh64/kernel/init_task.c b/arch/sh64/kernel/init_task.c new file mode 100644 index 00000000000..de2d07db1f8 --- /dev/null +++ b/arch/sh64/kernel/init_task.c @@ -0,0 +1,46 @@ +/* + * 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. + * + * arch/sh64/kernel/init_task.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003 Paul Mundt + * + */ +#include <linux/rwsem.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/init_task.h> +#include <linux/mqueue.h> + +#include <asm/uaccess.h> +#include <asm/pgtable.h> + +static struct fs_struct init_fs = INIT_FS; +static struct files_struct init_files = INIT_FILES; +static struct signal_struct init_signals = INIT_SIGNALS(init_signals); +static struct sighand_struct init_sighand = INIT_SIGHAND(init_sighand); +struct mm_struct init_mm = INIT_MM(init_mm); + +struct pt_regs fake_swapper_regs; + +/* + * Initial thread structure. + * + * We need to make sure that this is THREAD_SIZE-byte aligned due + * to the way process stacks are handled. This is done by having a + * special "init_task" linker map entry.. + */ +union thread_union init_thread_union + __attribute__((__section__(".data.init_task"))) = + { INIT_THREAD_INFO(init_task) }; + +/* + * Initial task structure. + * + * All other task structs will be allocated on slabs in fork.c + */ +struct task_struct init_task = INIT_TASK(init_task); + diff --git a/arch/sh64/kernel/irq.c b/arch/sh64/kernel/irq.c new file mode 100644 index 00000000000..9fc2b71dbd8 --- /dev/null +++ b/arch/sh64/kernel/irq.c @@ -0,0 +1,116 @@ +/* + * 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. + * + * arch/sh64/kernel/irq.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003 Paul Mundt + * + */ + +/* + * IRQs are in fact implemented a bit like signal handlers for the kernel. + * Naturally it's not a 1:1 relation, but there are similarities. + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/timex.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/seq_file.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/smp.h> +#include <asm/pgalloc.h> +#include <asm/delay.h> +#include <asm/irq.h> +#include <linux/irq.h> + +void ack_bad_irq(unsigned int irq) +{ + printk("unexpected IRQ trap at irq %02x\n", irq); +} + +#if defined(CONFIG_PROC_FS) +int show_interrupts(struct seq_file *p, void *v) +{ + int i = *(loff_t *) v, j; + struct irqaction * action; + unsigned long flags; + + if (i == 0) { + seq_puts(p, " "); + for (j=0; j<NR_CPUS; j++) + if (cpu_online(j)) + seq_printf(p, "CPU%d ",j); + seq_putc(p, '\n'); + } + + if (i < NR_IRQS) { + spin_lock_irqsave(&irq_desc[i].lock, flags); + action = irq_desc[i].action; + if (!action) + goto unlock; + seq_printf(p, "%3d: ",i); + seq_printf(p, "%10u ", kstat_irqs(i)); + seq_printf(p, " %14s", irq_desc[i].handler->typename); + seq_printf(p, " %s", action->name); + + for (action=action->next; action; action = action->next) + seq_printf(p, ", %s", action->name); + seq_putc(p, '\n'); +unlock: + spin_unlock_irqrestore(&irq_desc[i].lock, flags); + } + return 0; +} +#endif + +/* + * do_NMI handles all Non-Maskable Interrupts. + */ +asmlinkage void do_NMI(unsigned long vector_num, struct pt_regs * regs) +{ + if (regs->sr & 0x40000000) + printk("unexpected NMI trap in system mode\n"); + else + printk("unexpected NMI trap in user mode\n"); + + /* No statistics */ +} + +/* + * do_IRQ handles all normal device IRQ's. + */ +asmlinkage int do_IRQ(unsigned long vector_num, struct pt_regs * regs) +{ + int irq; + + irq_enter(); + + irq = irq_demux(vector_num); + + if (irq >= 0) { + __do_IRQ(irq, regs); + } else { + printk("unexpected IRQ trap at vector %03lx\n", vector_num); + } + + irq_exit(); + + return 1; +} + diff --git a/arch/sh64/kernel/irq_intc.c b/arch/sh64/kernel/irq_intc.c new file mode 100644 index 00000000000..43f88f3a78b --- /dev/null +++ b/arch/sh64/kernel/irq_intc.c @@ -0,0 +1,272 @@ +/* + * 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. + * + * arch/sh64/kernel/irq_intc.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003 Paul Mundt + * + * Interrupt Controller support for SH5 INTC. + * Per-interrupt selective. IRLM=0 (Fixed priority) is not + * supported being useless without a cascaded interrupt + * controller. + * + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/stddef.h> +#include <linux/bitops.h> /* this includes also <asm/registers.h */ + /* which is required to remap register */ + /* names used into __asm__ blocks... */ + +#include <asm/hardware.h> +#include <asm/platform.h> +#include <asm/page.h> +#include <asm/io.h> +#include <asm/irq.h> + +/* + * Maybe the generic Peripheral block could move to a more + * generic include file. INTC Block will be defined here + * and only here to make INTC self-contained in a single + * file. + */ +#define INTC_BLOCK_OFFSET 0x01000000 + +/* Base */ +#define INTC_BASE PHYS_PERIPHERAL_BLOCK + \ + INTC_BLOCK_OFFSET + +/* Address */ +#define INTC_ICR_SET (intc_virt + 0x0) +#define INTC_ICR_CLEAR (intc_virt + 0x8) +#define INTC_INTPRI_0 (intc_virt + 0x10) +#define INTC_INTSRC_0 (intc_virt + 0x50) +#define INTC_INTSRC_1 (intc_virt + 0x58) +#define INTC_INTREQ_0 (intc_virt + 0x60) +#define INTC_INTREQ_1 (intc_virt + 0x68) +#define INTC_INTENB_0 (intc_virt + 0x70) +#define INTC_INTENB_1 (intc_virt + 0x78) +#define INTC_INTDSB_0 (intc_virt + 0x80) +#define INTC_INTDSB_1 (intc_virt + 0x88) + +#define INTC_ICR_IRLM 0x1 +#define INTC_INTPRI_PREGS 8 /* 8 Priority Registers */ +#define INTC_INTPRI_PPREG 8 /* 8 Priorities per Register */ + + +/* + * Mapper between the vector ordinal and the IRQ number + * passed to kernel/device drivers. + */ +int intc_evt_to_irq[(0xE20/0x20)+1] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 0x000 - 0x0E0 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 0x100 - 0x1E0 */ + 0, 0, 0, 0, 0, 1, 0, 0, /* 0x200 - 0x2E0 */ + 2, 0, 0, 3, 0, 0, 0, -1, /* 0x300 - 0x3E0 */ + 32, 33, 34, 35, 36, 37, 38, -1, /* 0x400 - 0x4E0 */ + -1, -1, -1, 63, -1, -1, -1, -1, /* 0x500 - 0x5E0 */ + -1, -1, 18, 19, 20, 21, 22, -1, /* 0x600 - 0x6E0 */ + 39, 40, 41, 42, -1, -1, -1, -1, /* 0x700 - 0x7E0 */ + 4, 5, 6, 7, -1, -1, -1, -1, /* 0x800 - 0x8E0 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 0x900 - 0x9E0 */ + 12, 13, 14, 15, 16, 17, -1, -1, /* 0xA00 - 0xAE0 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB00 - 0xBE0 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC00 - 0xCE0 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD00 - 0xDE0 */ + -1, -1 /* 0xE00 - 0xE20 */ +}; + +/* + * Opposite mapper. + */ +static int IRQ_to_vectorN[NR_INTC_IRQS] = { + 0x12, 0x15, 0x18, 0x1B, 0x40, 0x41, 0x42, 0x43, /* 0- 7 */ + -1, -1, -1, -1, 0x50, 0x51, 0x52, 0x53, /* 8-15 */ + 0x54, 0x55, 0x32, 0x33, 0x34, 0x35, 0x36, -1, /* 16-23 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 24-31 */ + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x38, /* 32-39 */ + 0x39, 0x3A, 0x3B, -1, -1, -1, -1, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-55 */ + -1, -1, -1, -1, -1, -1, -1, 0x2B, /* 56-63 */ + +}; + +static unsigned long intc_virt; + +static unsigned int startup_intc_irq(unsigned int irq); +static void shutdown_intc_irq(unsigned int irq); +static void enable_intc_irq(unsigned int irq); +static void disable_intc_irq(unsigned int irq); +static void mask_and_ack_intc(unsigned int); +static void end_intc_irq(unsigned int irq); + +static struct hw_interrupt_type intc_irq_type = { + "INTC", + startup_intc_irq, + shutdown_intc_irq, + enable_intc_irq, + disable_intc_irq, + mask_and_ack_intc, + end_intc_irq +}; + +static int irlm; /* IRL mode */ + +static unsigned int startup_intc_irq(unsigned int irq) +{ + enable_intc_irq(irq); + return 0; /* never anything pending */ +} + +static void shutdown_intc_irq(unsigned int irq) +{ + disable_intc_irq(irq); +} + +static void enable_intc_irq(unsigned int irq) +{ + unsigned long reg; + unsigned long bitmask; + + if ((irq <= IRQ_IRL3) && (irlm == NO_PRIORITY)) + printk("Trying to use straight IRL0-3 with an encoding platform.\n"); + + if (irq < 32) { + reg = INTC_INTENB_0; + bitmask = 1 << irq; + } else { + reg = INTC_INTENB_1; + bitmask = 1 << (irq - 32); + } + + ctrl_outl(bitmask, reg); +} + +static void disable_intc_irq(unsigned int irq) +{ + unsigned long reg; + unsigned long bitmask; + + if (irq < 32) { + reg = INTC_INTDSB_0; + bitmask = 1 << irq; + } else { + reg = INTC_INTDSB_1; + bitmask = 1 << (irq - 32); + } + + ctrl_outl(bitmask, reg); +} + +static void mask_and_ack_intc(unsigned int irq) +{ + disable_intc_irq(irq); +} + +static void end_intc_irq(unsigned int irq) +{ + enable_intc_irq(irq); +} + +/* For future use, if we ever support IRLM=0) */ +void make_intc_irq(unsigned int irq) +{ + disable_irq_nosync(irq); + irq_desc[irq].handler = &intc_irq_type; + disable_intc_irq(irq); +} + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_SYSCTL) +int intc_irq_describe(char* p, int irq) +{ + if (irq < NR_INTC_IRQS) + return sprintf(p, "(0x%3x)", IRQ_to_vectorN[irq]*0x20); + else + return 0; +} +#endif + +void __init init_IRQ(void) +{ + unsigned long long __dummy0, __dummy1=~0x00000000100000f0; + unsigned long reg; + unsigned long data; + int i; + + intc_virt = onchip_remap(INTC_BASE, 1024, "INTC"); + if (!intc_virt) { + panic("Unable to remap INTC\n"); + } + + + /* Set default: per-line enable/disable, priority driven ack/eoi */ + for (i = 0; i < NR_INTC_IRQS; i++) { + if (platform_int_priority[i] != NO_PRIORITY) { + irq_desc[i].handler = &intc_irq_type; + } + } + + + /* Disable all interrupts and set all priorities to 0 to avoid trouble */ + ctrl_outl(-1, INTC_INTDSB_0); + ctrl_outl(-1, INTC_INTDSB_1); + + for (reg = INTC_INTPRI_0, i = 0; i < INTC_INTPRI_PREGS; i++, reg += 8) + ctrl_outl( NO_PRIORITY, reg); + + + /* Set IRLM */ + /* If all the priorities are set to 'no priority', then + * assume we are using encoded mode. + */ + irlm = platform_int_priority[IRQ_IRL0] + platform_int_priority[IRQ_IRL1] + \ + platform_int_priority[IRQ_IRL2] + platform_int_priority[IRQ_IRL3]; + + if (irlm == NO_PRIORITY) { + /* IRLM = 0 */ + reg = INTC_ICR_CLEAR; + i = IRQ_INTA; + printk("Trying to use encoded IRL0-3. IRLs unsupported.\n"); + } else { + /* IRLM = 1 */ + reg = INTC_ICR_SET; + i = IRQ_IRL0; + } + ctrl_outl(INTC_ICR_IRLM, reg); + + /* Set interrupt priorities according to platform description */ + for (data = 0, reg = INTC_INTPRI_0; i < NR_INTC_IRQS; i++) { + data |= platform_int_priority[i] << ((i % INTC_INTPRI_PPREG) * 4); + if ((i % INTC_INTPRI_PPREG) == (INTC_INTPRI_PPREG - 1)) { + /* Upon the 7th, set Priority Register */ + ctrl_outl(data, reg); + data = 0; + reg += 8; + } + } + +#ifdef CONFIG_SH_CAYMAN + { + extern void init_cayman_irq(void); + + init_cayman_irq(); + } +#endif + + /* + * And now let interrupts come in. + * sti() is not enough, we need to + * lower priority, too. + */ + __asm__ __volatile__("getcon " __SR ", %0\n\t" + "and %0, %1, %0\n\t" + "putcon %0, " __SR "\n\t" + : "=&r" (__dummy0) + : "r" (__dummy1)); +} diff --git a/arch/sh64/kernel/led.c b/arch/sh64/kernel/led.c new file mode 100644 index 00000000000..cf993c4a9fd --- /dev/null +++ b/arch/sh64/kernel/led.c @@ -0,0 +1,41 @@ +/* + * arch/sh64/kernel/led.c + * + * Copyright (C) 2002 Stuart Menefy <stuart.menefy@st.com> + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Flash the LEDs + */ +#include <linux/config.h> +#include <linux/stddef.h> +#include <linux/sched.h> + +void mach_led(int pos, int val); + +/* acts like an actual heart beat -- ie thump-thump-pause... */ +void heartbeat(void) +{ + static unsigned int cnt = 0, period = 0, dist = 0; + + if (cnt == 0 || cnt == dist) { + mach_led(-1, 1); + } else if (cnt == 7 || cnt == dist + 7) { + mach_led(-1, 0); + } + + if (++cnt > period) { + cnt = 0; + + /* + * The hyperbolic function below modifies the heartbeat period + * length in dependency of the current (5min) load. It goes + * through the points f(0)=126, f(1)=86, f(5)=51, f(inf)->30. + */ + period = ((672 << FSHIFT) / (5 * avenrun[0] + + (7 << FSHIFT))) + 30; + dist = period / 4; + } +} + diff --git a/arch/sh64/kernel/module.c b/arch/sh64/kernel/module.c new file mode 100644 index 00000000000..2598f6b88b4 --- /dev/null +++ b/arch/sh64/kernel/module.c @@ -0,0 +1,161 @@ +/* Kernel module help for sh64. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Copyright 2004 SuperH (UK) Ltd + Author: Richard Curnow + + Based on the sh version, and on code from the sh64-specific parts of + modutils, originally written by Richard Curnow and Ben Gaster. + +*/ +#include <linux/moduleloader.h> +#include <linux/elf.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/kernel.h> + +#if 0 +#define DEBUGP printk +#else +#define DEBUGP(fmt...) +#endif + +void *module_alloc(unsigned long size) +{ + if (size == 0) + return NULL; + return vmalloc(size); +} + + +/* Free memory returned from module_alloc */ +void module_free(struct module *mod, void *module_region) +{ + vfree(module_region); + /* FIXME: If module_region == mod->init_region, trim exception + table entries. */ +} + +/* We don't need anything special. */ +int module_frob_arch_sections(Elf_Ehdr *hdr, + Elf_Shdr *sechdrs, + char *secstrings, + struct module *mod) +{ + return 0; +} + +int apply_relocate_add(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + unsigned int i; + Elf32_Rela *rel = (void *)sechdrs[relsec].sh_addr; + Elf32_Sym *sym; + Elf32_Addr relocation; + uint32_t *location; + int align; + int is_shmedia; + + DEBUGP("Applying relocate section %u to %u\n", relsec, + sechdrs[relsec].sh_info); + for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) { + /* This is where to make the change */ + location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + + rel[i].r_offset; + /* This is the symbol it is referring to. Note that all + undefined symbols have been resolved. */ + sym = (Elf32_Sym *)sechdrs[symindex].sh_addr + + ELF32_R_SYM(rel[i].r_info); + relocation = sym->st_value + rel[i].r_addend; + align = (int)location & 3; + + /* For text addresses, bit2 of the st_other field indicates + * whether the symbol is SHmedia (1) or SHcompact (0). If + * SHmedia, the LSB of the symbol needs to be asserted + * for the CPU to be in SHmedia mode when it starts executing + * the branch target. */ + is_shmedia = (sym->st_other & 4) ? 1 : 0; + if (is_shmedia) { + relocation |= 1; + } + + switch (ELF32_R_TYPE(rel[i].r_info)) { + case R_SH_DIR32: + DEBUGP("R_SH_DIR32 @%08lx = %08lx\n", (unsigned long) location, (unsigned long) relocation); + *location += relocation; + break; + case R_SH_REL32: + DEBUGP("R_SH_REL32 @%08lx = %08lx\n", (unsigned long) location, (unsigned long) relocation); + relocation -= (Elf32_Addr) location; + *location += relocation; + break; + case R_SH_IMM_LOW16: + DEBUGP("R_SH_IMM_LOW16 @%08lx = %08lx\n", (unsigned long) location, (unsigned long) relocation); + *location = (*location & ~0x3fffc00) | + ((relocation & 0xffff) << 10); + break; + case R_SH_IMM_MEDLOW16: + DEBUGP("R_SH_IMM_MEDLOW16 @%08lx = %08lx\n", (unsigned long) location, (unsigned long) relocation); + *location = (*location & ~0x3fffc00) | + (((relocation >> 16) & 0xffff) << 10); + break; + case R_SH_IMM_LOW16_PCREL: + DEBUGP("R_SH_IMM_LOW16_PCREL @%08lx = %08lx\n", (unsigned long) location, (unsigned long) relocation); + relocation -= (Elf32_Addr) location; + *location = (*location & ~0x3fffc00) | + ((relocation & 0xffff) << 10); + break; + case R_SH_IMM_MEDLOW16_PCREL: + DEBUGP("R_SH_IMM_MEDLOW16_PCREL @%08lx = %08lx\n", (unsigned long) location, (unsigned long) relocation); + relocation -= (Elf32_Addr) location; + *location = (*location & ~0x3fffc00) | + (((relocation >> 16) & 0xffff) << 10); + break; + default: + printk(KERN_ERR "module %s: Unknown relocation: %u\n", + me->name, ELF32_R_TYPE(rel[i].r_info)); + return -ENOEXEC; + } + } + return 0; +} + +int apply_relocate(Elf32_Shdr *sechdrs, + const char *strtab, + unsigned int symindex, + unsigned int relsec, + struct module *me) +{ + printk(KERN_ERR "module %s: REL RELOCATION unsupported\n", + me->name); + return -ENOEXEC; +} + +int module_finalize(const Elf_Ehdr *hdr, + const Elf_Shdr *sechdrs, + struct module *me) +{ + return 0; +} + +void module_arch_cleanup(struct module *mod) +{ +} + diff --git a/arch/sh64/kernel/pci-dma.c b/arch/sh64/kernel/pci-dma.c new file mode 100644 index 00000000000..a36c3d71a3f --- /dev/null +++ b/arch/sh64/kernel/pci-dma.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2001 David J. Mckay (david.mckay@st.com) + * Copyright (C) 2003 Paul Mundt (lethal@linux-sh.org) + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Dynamic DMA mapping support. + */ +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/pci.h> +#include <asm/io.h> + +void *consistent_alloc(struct pci_dev *hwdev, size_t size, + dma_addr_t *dma_handle) +{ + void *ret; + int gfp = GFP_ATOMIC; + void *vp; + + if (hwdev == NULL || hwdev->dma_mask != 0xffffffff) + gfp |= GFP_DMA; + + ret = (void *)__get_free_pages(gfp, get_order(size)); + + /* now call our friend ioremap_nocache to give us an uncached area */ + vp = ioremap_nocache(virt_to_phys(ret), size); + + if (vp != NULL) { + memset(vp, 0, size); + *dma_handle = virt_to_bus(ret); + dma_cache_wback_inv((unsigned long)ret, size); + } + + return vp; +} + +void consistent_free(struct pci_dev *hwdev, size_t size, + void *vaddr, dma_addr_t dma_handle) +{ + void *alloc; + + alloc = bus_to_virt((unsigned long)dma_handle); + free_pages((unsigned long)alloc, get_order(size)); + + iounmap(vaddr); +} + diff --git a/arch/sh64/kernel/pci_sh5.c b/arch/sh64/kernel/pci_sh5.c new file mode 100644 index 00000000000..6197879e857 --- /dev/null +++ b/arch/sh64/kernel/pci_sh5.c @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2001 David J. Mckay (david.mckay@st.com) + * Copyright (C) 2003, 2004 Paul Mundt + * Copyright (C) 2004 Richard Curnow + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Support functions for the SH5 PCI hardware. + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/rwsem.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <asm/pci.h> +#include <linux/irq.h> + +#include <asm/io.h> +#include <asm/hardware.h> +#include "pci_sh5.h" + +static unsigned long pcicr_virt; +unsigned long pciio_virt; + +static void __init pci_fixup_ide_bases(struct pci_dev *d) +{ + int i; + + /* + * PCI IDE controllers use non-standard I/O port decoding, respect it. + */ + if ((d->class >> 8) != PCI_CLASS_STORAGE_IDE) + return; + printk("PCI: IDE base address fixup for %s\n", pci_name(d)); + for(i=0; i<4; i++) { + struct resource *r = &d->resource[i]; + if ((r->start & ~0x80) == 0x374) { + r->start |= 2; + r->end = r->start; + } + } +} +DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, pci_fixup_ide_bases); + +char * __init pcibios_setup(char *str) +{ + return str; +} + +/* Rounds a number UP to the nearest power of two. Used for + * sizing the PCI window. + */ +static u32 __init r2p2(u32 num) +{ + int i = 31; + u32 tmp = num; + + if (num == 0) + return 0; + + do { + if (tmp & (1 << 31)) + break; + i--; + tmp <<= 1; + } while (i >= 0); + + tmp = 1 << i; + /* If the original number isn't a power of 2, round it up */ + if (tmp != num) + tmp <<= 1; + + return tmp; +} + +extern unsigned long long memory_start, memory_end; + +int __init sh5pci_init(unsigned memStart, unsigned memSize) +{ + u32 lsr0; + u32 uval; + + pcicr_virt = onchip_remap(SH5PCI_ICR_BASE, 1024, "PCICR"); + if (!pcicr_virt) { + panic("Unable to remap PCICR\n"); + } + + pciio_virt = onchip_remap(SH5PCI_IO_BASE, 0x10000, "PCIIO"); + if (!pciio_virt) { + panic("Unable to remap PCIIO\n"); + } + + pr_debug("Register base addres is 0x%08lx\n", pcicr_virt); + + /* Clear snoop registers */ + SH5PCI_WRITE(CSCR0, 0); + SH5PCI_WRITE(CSCR1, 0); + + pr_debug("Wrote to reg\n"); + + /* Switch off interrupts */ + SH5PCI_WRITE(INTM, 0); + SH5PCI_WRITE(AINTM, 0); + SH5PCI_WRITE(PINTM, 0); + + /* Set bus active, take it out of reset */ + uval = SH5PCI_READ(CR); + + /* Set command Register */ + SH5PCI_WRITE(CR, uval | CR_LOCK_MASK | CR_CFINT| CR_FTO | CR_PFE | CR_PFCS | CR_BMAM); + + uval=SH5PCI_READ(CR); + pr_debug("CR is actually 0x%08x\n",uval); + + /* Allow it to be a master */ + /* NB - WE DISABLE I/O ACCESS to stop overlap */ + /* set WAIT bit to enable stepping, an attempt to improve stability */ + SH5PCI_WRITE_SHORT(CSR_CMD, + PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_WAIT); + + /* + ** Set translation mapping memory in order to convert the address + ** used for the main bus, to the PCI internal address. + */ + SH5PCI_WRITE(MBR,0x40000000); + + /* Always set the max size 512M */ + SH5PCI_WRITE(MBMR, PCISH5_MEM_SIZCONV(512*1024*1024)); + + /* + ** I/O addresses are mapped at internal PCI specific address + ** as is described into the configuration bridge table. + ** These are changed to 0, to allow cards that have legacy + ** io such as vga to function correctly. We set the SH5 IOBAR to + ** 256K, which is a bit big as we can only have 64K of address space + */ + + SH5PCI_WRITE(IOBR,0x0); + + pr_debug("PCI:Writing 0x%08x to IOBR\n",0); + + /* Set up a 256K window. Totally pointless waste of address space */ + SH5PCI_WRITE(IOBMR,0); + pr_debug("PCI:Writing 0x%08x to IOBMR\n",0); + + /* The SH5 has a HUGE 256K I/O region, which breaks the PCI spec. Ideally, + * we would want to map the I/O region somewhere, but it is so big this is not + * that easy! + */ + SH5PCI_WRITE(CSR_IBAR0,~0); + /* Set memory size value */ + memSize = memory_end - memory_start; + + /* Now we set up the mbars so the PCI bus can see the memory of the machine */ + if (memSize < (1024 * 1024)) { + printk(KERN_ERR "PCISH5: Ridiculous memory size of 0x%x?\n", memSize); + return -EINVAL; + } + + /* Set LSR 0 */ + lsr0 = (memSize > (512 * 1024 * 1024)) ? 0x1ff00001 : ((r2p2(memSize) - 0x100000) | 0x1); + SH5PCI_WRITE(LSR0, lsr0); + + pr_debug("PCI:Writing 0x%08x to LSR0\n",lsr0); + + /* Set MBAR 0 */ + SH5PCI_WRITE(CSR_MBAR0, memory_start); + SH5PCI_WRITE(LAR0, memory_start); + + SH5PCI_WRITE(CSR_MBAR1,0); + SH5PCI_WRITE(LAR1,0); + SH5PCI_WRITE(LSR1,0); + + pr_debug("PCI:Writing 0x%08llx to CSR_MBAR0\n",memory_start); + pr_debug("PCI:Writing 0x%08llx to LAR0\n",memory_start); + + /* Enable the PCI interrupts on the device */ + SH5PCI_WRITE(INTM, ~0); + SH5PCI_WRITE(AINTM, ~0); + SH5PCI_WRITE(PINTM, ~0); + + pr_debug("Switching on all error interrupts\n"); + + return(0); +} + +static int sh5pci_read(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 *val) +{ + SH5PCI_WRITE(PAR, CONFIG_CMD(bus, devfn, where)); + + switch (size) { + case 1: + *val = (u8)SH5PCI_READ_BYTE(PDR + (where & 3)); + break; + case 2: + *val = (u16)SH5PCI_READ_SHORT(PDR + (where & 2)); + break; + case 4: + *val = SH5PCI_READ(PDR); + break; + } + + return PCIBIOS_SUCCESSFUL; +} + +static int sh5pci_write(struct pci_bus *bus, unsigned int devfn, int where, + int size, u32 val) +{ + SH5PCI_WRITE(PAR, CONFIG_CMD(bus, devfn, where)); + + switch (size) { + case 1: + SH5PCI_WRITE_BYTE(PDR + (where & 3), (u8)val); + break; + case 2: + SH5PCI_WRITE_SHORT(PDR + (where & 2), (u16)val); + break; + case 4: + SH5PCI_WRITE(PDR, val); + break; + } + + return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops pci_config_ops = { + .read = sh5pci_read, + .write = sh5pci_write, +}; + +/* Everything hangs off this */ +static struct pci_bus *pci_root_bus; + + +static u8 __init no_swizzle(struct pci_dev *dev, u8 * pin) +{ + pr_debug("swizzle for dev %d on bus %d slot %d pin is %d\n", + dev->devfn,dev->bus->number, PCI_SLOT(dev->devfn),*pin); + return PCI_SLOT(dev->devfn); +} + +static inline u8 bridge_swizzle(u8 pin, u8 slot) +{ + return (((pin-1) + slot) % 4) + 1; +} + +u8 __init common_swizzle(struct pci_dev *dev, u8 *pinp) +{ + if (dev->bus->number != 0) { + u8 pin = *pinp; + do { + pin = bridge_swizzle(pin, PCI_SLOT(dev->devfn)); + /* Move up the chain of bridges. */ + dev = dev->bus->self; + } while (dev->bus->self); + *pinp = pin; + + /* The slot is the slot of the last bridge. */ + } + + return PCI_SLOT(dev->devfn); +} + +/* This needs to be shunted out of here into the board specific bit */ + +static int __init map_cayman_irq(struct pci_dev *dev, u8 slot, u8 pin) +{ + int result = -1; + + /* The complication here is that the PCI IRQ lines from the Cayman's 2 + 5V slots get into the CPU via a different path from the IRQ lines + from the 3 3.3V slots. Thus, we have to detect whether the card's + interrupts go via the 5V or 3.3V path, i.e. the 'bridge swizzling' + at the point where we cross from 5V to 3.3V is not the normal case. + + The added complication is that we don't know that the 5V slots are + always bus 2, because a card containing a PCI-PCI bridge may be + plugged into a 3.3V slot, and this changes the bus numbering. + + Also, the Cayman has an intermediate PCI bus that goes a custom + expansion board header (and to the secondary bridge). This bus has + never been used in practice. + + The 1ary onboard PCI-PCI bridge is device 3 on bus 0 + The 2ary onboard PCI-PCI bridge is device 0 on the 2ary bus of the 1ary bridge. + */ + + struct slot_pin { + int slot; + int pin; + } path[4]; + int i=0; + + while (dev->bus->number > 0) { + + slot = path[i].slot = PCI_SLOT(dev->devfn); + pin = path[i].pin = bridge_swizzle(pin, slot); + dev = dev->bus->self; + i++; + if (i > 3) panic("PCI path to root bus too long!\n"); + } + + slot = PCI_SLOT(dev->devfn); + /* This is the slot on bus 0 through which the device is eventually + reachable. */ + + /* Now work back up. */ + if ((slot < 3) || (i == 0)) { + /* Bus 0 (incl. PCI-PCI bridge itself) : perform the final + swizzle now. */ + result = IRQ_INTA + bridge_swizzle(pin, slot) - 1; + } else { + i--; + slot = path[i].slot; + pin = path[i].pin; + if (slot > 0) { + panic("PCI expansion bus device found - not handled!\n"); + } else { + if (i > 0) { + /* 5V slots */ + i--; + slot = path[i].slot; + pin = path[i].pin; + /* 'pin' was swizzled earlier wrt slot, don't do it again. */ + result = IRQ_P2INTA + (pin - 1); + } else { + /* IRQ for 2ary PCI-PCI bridge : unused */ + result = -1; + } + } + } + + return result; +} + +irqreturn_t pcish5_err_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned pci_int, pci_air, pci_cir, pci_aint; + + pci_int = SH5PCI_READ(INT); + pci_cir = SH5PCI_READ(CIR); + pci_air = SH5PCI_READ(AIR); + + if (pci_int) { + printk("PCI INTERRUPT (at %08llx)!\n", regs->pc); + printk("PCI INT -> 0x%x\n", pci_int & 0xffff); + printk("PCI AIR -> 0x%x\n", pci_air); + printk("PCI CIR -> 0x%x\n", pci_cir); + SH5PCI_WRITE(INT, ~0); + } + + pci_aint = SH5PCI_READ(AINT); + if (pci_aint) { + printk("PCI ARB INTERRUPT!\n"); + printk("PCI AINT -> 0x%x\n", pci_aint); + printk("PCI AIR -> 0x%x\n", pci_air); + printk("PCI CIR -> 0x%x\n", pci_cir); + SH5PCI_WRITE(AINT, ~0); + } + + return IRQ_HANDLED; +} + +irqreturn_t pcish5_serr_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + printk("SERR IRQ\n"); + + return IRQ_NONE; +} + +#define ROUND_UP(x, a) (((x) + (a) - 1) & ~((a) - 1)) + +static void __init +pcibios_size_bridge(struct pci_bus *bus, struct resource *ior, + struct resource *memr) +{ + struct resource io_res, mem_res; + struct pci_dev *dev; + struct pci_dev *bridge = bus->self; + struct list_head *ln; + + if (!bridge) + return; /* host bridge, nothing to do */ + + /* set reasonable default locations for pcibios_align_resource */ + io_res.start = PCIBIOS_MIN_IO; + mem_res.start = PCIBIOS_MIN_MEM; + + io_res.end = io_res.start; + mem_res.end = mem_res.start; + + /* Collect information about how our direct children are layed out. */ + for (ln=bus->devices.next; ln != &bus->devices; ln=ln->next) { + int i; + dev = pci_dev_b(ln); + + /* Skip bridges for now */ + if (dev->class >> 8 == PCI_CLASS_BRIDGE_PCI) + continue; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource res; + unsigned long size; + + memcpy(&res, &dev->resource[i], sizeof(res)); + size = res.end - res.start + 1; + + if (res.flags & IORESOURCE_IO) { + res.start = io_res.end; + pcibios_align_resource(dev, &res, size, 0); + io_res.end = res.start + size; + } else if (res.flags & IORESOURCE_MEM) { + res.start = mem_res.end; + pcibios_align_resource(dev, &res, size, 0); + mem_res.end = res.start + size; + } + } + } + + /* And for all of the subordinate busses. */ + for (ln=bus->children.next; ln != &bus->children; ln=ln->next) + pcibios_size_bridge(pci_bus_b(ln), &io_res, &mem_res); + + /* turn the ending locations into sizes (subtract start) */ + io_res.end -= io_res.start; + mem_res.end -= mem_res.start; + + /* Align the sizes up by bridge rules */ + io_res.end = ROUND_UP(io_res.end, 4*1024) - 1; + mem_res.end = ROUND_UP(mem_res.end, 1*1024*1024) - 1; + + /* Adjust the bridge's allocation requirements */ + bridge->resource[0].end = bridge->resource[0].start + io_res.end; + bridge->resource[1].end = bridge->resource[1].start + mem_res.end; + + bridge->resource[PCI_BRIDGE_RESOURCES].end = + bridge->resource[PCI_BRIDGE_RESOURCES].start + io_res.end; + bridge->resource[PCI_BRIDGE_RESOURCES+1].end = + bridge->resource[PCI_BRIDGE_RESOURCES+1].start + mem_res.end; + + /* adjust parent's resource requirements */ + if (ior) { + ior->end = ROUND_UP(ior->end, 4*1024); + ior->end += io_res.end; + } + + if (memr) { + memr->end = ROUND_UP(memr->end, 1*1024*1024); + memr->end += mem_res.end; + } +} + +#undef ROUND_UP + +static void __init pcibios_size_bridges(void) +{ + struct resource io_res, mem_res; + + memset(&io_res, 0, sizeof(io_res)); + memset(&mem_res, 0, sizeof(mem_res)); + + pcibios_size_bridge(pci_root_bus, &io_res, &mem_res); +} + +static int __init pcibios_init(void) +{ + if (request_irq(IRQ_ERR, pcish5_err_irq, + SA_INTERRUPT, "PCI Error",NULL) < 0) { + printk(KERN_ERR "PCISH5: Cannot hook PCI_PERR interrupt\n"); + return -EINVAL; + } + + if (request_irq(IRQ_SERR, pcish5_serr_irq, + SA_INTERRUPT, "PCI SERR interrupt", NULL) < 0) { + printk(KERN_ERR "PCISH5: Cannot hook PCI_SERR interrupt\n"); + return -EINVAL; + } + + /* The pci subsytem needs to know where memory is and how much + * of it there is. I've simply made these globals. A better mechanism + * is probably needed. + */ + sh5pci_init(__pa(memory_start), + __pa(memory_end) - __pa(memory_start)); + + pci_root_bus = pci_scan_bus(0, &pci_config_ops, NULL); + pcibios_size_bridges(); + pci_assign_unassigned_resources(); + pci_fixup_irqs(no_swizzle, map_cayman_irq); + + return 0; +} + +subsys_initcall(pcibios_init); + +void __init pcibios_fixup_bus(struct pci_bus *bus) +{ + struct pci_dev *dev = bus->self; + int i; + +#if 1 + if(dev) { + for(i=0; i<3; i++) { + bus->resource[i] = + &dev->resource[PCI_BRIDGE_RESOURCES+i]; + bus->resource[i]->name = bus->name; + } + bus->resource[0]->flags |= IORESOURCE_IO; + bus->resource[1]->flags |= IORESOURCE_MEM; + + /* For now, propagate host limits to the bus; + * we'll adjust them later. */ + +#if 1 + bus->resource[0]->end = 64*1024 - 1 ; + bus->resource[1]->end = PCIBIOS_MIN_MEM+(256*1024*1024)-1; + bus->resource[0]->start = PCIBIOS_MIN_IO; + bus->resource[1]->start = PCIBIOS_MIN_MEM; +#else + bus->resource[0]->end = 0 + bus->resource[1]->end = 0 + bus->resource[0]->start =0 + bus->resource[1]->start = 0; +#endif + /* Turn off downstream PF memory address range by default */ + bus->resource[2]->start = 1024*1024; + bus->resource[2]->end = bus->resource[2]->start - 1; + } +#endif + +} + diff --git a/arch/sh64/kernel/pci_sh5.h b/arch/sh64/kernel/pci_sh5.h new file mode 100644 index 00000000000..8f21f5d2aa3 --- /dev/null +++ b/arch/sh64/kernel/pci_sh5.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2001 David J. Mckay (david.mckay@st.com) + * + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Defintions for the SH5 PCI hardware. + */ + +/* Product ID */ +#define PCISH5_PID 0x350d + +/* vendor ID */ +#define PCISH5_VID 0x1054 + +/* Configuration types */ +#define ST_TYPE0 0x00 /* Configuration cycle type 0 */ +#define ST_TYPE1 0x01 /* Configuration cycle type 1 */ + +/* VCR data */ +#define PCISH5_VCR_STATUS 0x00 +#define PCISH5_VCR_VERSION 0x08 + +/* +** ICR register offsets and bits +*/ +#define PCISH5_ICR_CR 0x100 /* PCI control register values */ +#define CR_PBAM (1<<12) +#define CR_PFCS (1<<11) +#define CR_FTO (1<<10) +#define CR_PFE (1<<9) +#define CR_TBS (1<<8) +#define CR_SPUE (1<<7) +#define CR_BMAM (1<<6) +#define CR_HOST (1<<5) +#define CR_CLKEN (1<<4) +#define CR_SOCS (1<<3) +#define CR_IOCS (1<<2) +#define CR_RSTCTL (1<<1) +#define CR_CFINT (1<<0) +#define CR_LOCK_MASK 0xa5000000 + +#define PCISH5_ICR_INT 0x114 /* Interrupt registert values */ +#define INT_MADIM (1<<2) + +#define PCISH5_ICR_LSR0 0X104 /* Local space register values */ +#define PCISH5_ICR_LSR1 0X108 /* Local space register values */ +#define PCISH5_ICR_LAR0 0x10c /* Local address register values */ +#define PCISH5_ICR_LAR1 0x110 /* Local address register values */ +#define PCISH5_ICR_INTM 0x118 /* Interrupt mask register values */ +#define PCISH5_ICR_AIR 0x11c /* Interrupt error address information register values */ +#define PCISH5_ICR_CIR 0x120 /* Interrupt error command information register values */ +#define PCISH5_ICR_AINT 0x130 /* Interrupt error arbiter interrupt register values */ +#define PCISH5_ICR_AINTM 0x134 /* Interrupt error arbiter interrupt mask register values */ +#define PCISH5_ICR_BMIR 0x138 /* Interrupt error info register of bus master values */ +#define PCISH5_ICR_PAR 0x1c0 /* Pio address register values */ +#define PCISH5_ICR_MBR 0x1c4 /* Memory space bank register values */ +#define PCISH5_ICR_IOBR 0x1c8 /* I/O space bank register values */ +#define PCISH5_ICR_PINT 0x1cc /* power management interrupt register values */ +#define PCISH5_ICR_PINTM 0x1d0 /* power management interrupt mask register values */ +#define PCISH5_ICR_MBMR 0x1d8 /* memory space bank mask register values */ +#define PCISH5_ICR_IOBMR 0x1dc /* I/O space bank mask register values */ +#define PCISH5_ICR_CSCR0 0x210 /* PCI cache snoop control register 0 */ +#define PCISH5_ICR_CSCR1 0x214 /* PCI cache snoop control register 1 */ +#define PCISH5_ICR_PDR 0x220 /* Pio data register values */ + +/* These are configs space registers */ +#define PCISH5_ICR_CSR_VID 0x000 /* Vendor id */ +#define PCISH5_ICR_CSR_DID 0x002 /* Device id */ +#define PCISH5_ICR_CSR_CMD 0x004 /* Command register */ +#define PCISH5_ICR_CSR_STATUS 0x006 /* Stautus */ +#define PCISH5_ICR_CSR_IBAR0 0x010 /* I/O base address register */ +#define PCISH5_ICR_CSR_MBAR0 0x014 /* First Memory base address register */ +#define PCISH5_ICR_CSR_MBAR1 0x018 /* Second Memory base address register */ + + + +/* Base address of registers */ +#define SH5PCI_ICR_BASE (PHYS_PCI_BLOCK + 0x00040000) +#define SH5PCI_IO_BASE (PHYS_PCI_BLOCK + 0x00800000) +/* #define SH5PCI_VCR_BASE (P2SEG_PCICB_BLOCK + P2SEG) */ + +/* Register selection macro */ +#define PCISH5_ICR_REG(x) ( pcicr_virt + (PCISH5_ICR_##x)) +/* #define PCISH5_VCR_REG(x) ( SH5PCI_VCR_BASE (PCISH5_VCR_##x)) */ + +/* Write I/O functions */ +#define SH5PCI_WRITE(reg,val) ctrl_outl((u32)(val),PCISH5_ICR_REG(reg)) +#define SH5PCI_WRITE_SHORT(reg,val) ctrl_outw((u16)(val),PCISH5_ICR_REG(reg)) +#define SH5PCI_WRITE_BYTE(reg,val) ctrl_outb((u8)(val),PCISH5_ICR_REG(reg)) + +/* Read I/O functions */ +#define SH5PCI_READ(reg) ctrl_inl(PCISH5_ICR_REG(reg)) +#define SH5PCI_READ_SHORT(reg) ctrl_inw(PCISH5_ICR_REG(reg)) +#define SH5PCI_READ_BYTE(reg) ctrl_inb(PCISH5_ICR_REG(reg)) + +/* Set PCI config bits */ +#define SET_CONFIG_BITS(bus,devfn,where) ((((bus) << 16) | ((devfn) << 8) | ((where) & ~3)) | 0x80000000) + +/* Set PCI command register */ +#define CONFIG_CMD(bus, devfn, where) SET_CONFIG_BITS(bus->number,devfn,where) + +/* Size converters */ +#define PCISH5_MEM_SIZCONV(x) (((x / 0x40000) - 1) << 18) +#define PCISH5_IO_SIZCONV(x) (((x / 0x40000) - 1) << 18) + + diff --git a/arch/sh64/kernel/pcibios.c b/arch/sh64/kernel/pcibios.c new file mode 100644 index 00000000000..50c61dcb9fa --- /dev/null +++ b/arch/sh64/kernel/pcibios.c @@ -0,0 +1,168 @@ +/* + * $Id: pcibios.c,v 1.1 2001/08/24 12:38:19 dwmw2 Exp $ + * + * arch/sh/kernel/pcibios.c + * + * Copyright (C) 2002 STMicroelectronics Limited + * Author : David J. McKay + * + * Copyright (C) 2004 Richard Curnow, SuperH UK Limited + * + * 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. + * This is GPL'd. + * + * Provided here are generic versions of: + * pcibios_update_resource() + * pcibios_align_resource() + * pcibios_enable_device() + * pcibios_set_master() + * pcibios_update_irq() + * + * These functions are collected here to reduce duplication of common + * code amongst the many platform-specific PCI support code files. + * + * Platform-specific files are expected to provide: + * pcibios_fixup_bus() + * pcibios_init() + * pcibios_setup() + * pcibios_fixup_pbus_ranges() + */ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/init.h> + +void +pcibios_update_resource(struct pci_dev *dev, struct resource *root, + struct resource *res, int resource) +{ + u32 new, check; + int reg; + + new = res->start | (res->flags & PCI_REGION_FLAG_MASK); + if (resource < 6) { + reg = PCI_BASE_ADDRESS_0 + 4*resource; + } else if (resource == PCI_ROM_RESOURCE) { + res->flags |= IORESOURCE_ROM_ENABLE; + new |= PCI_ROM_ADDRESS_ENABLE; + reg = dev->rom_base_reg; + } else { + /* Somebody might have asked allocation of a non-standard resource */ + return; + } + + pci_write_config_dword(dev, reg, new); + pci_read_config_dword(dev, reg, &check); + if ((new ^ check) & ((new & PCI_BASE_ADDRESS_SPACE_IO) ? PCI_BASE_ADDRESS_IO_MASK : PCI_BASE_ADDRESS_MEM_MASK)) { + printk(KERN_ERR "PCI: Error while updating region " + "%s/%d (%08x != %08x)\n", pci_name(dev), resource, + new, check); + } +} + +/* + * We need to avoid collisions with `mirrored' VGA ports + * and other strange ISA hardware, so we always want the + * addresses to be allocated in the 0x000-0x0ff region + * modulo 0x400. + */ +void pcibios_align_resource(void *data, struct resource *res, + unsigned long size, unsigned long align) +{ + if (res->flags & IORESOURCE_IO) { + unsigned long start = res->start; + + if (start & 0x300) { + start = (start + 0x3ff) & ~0x3ff; + res->start = start; + } + } +} + +static void pcibios_enable_bridge(struct pci_dev *dev) +{ + struct pci_bus *bus = dev->subordinate; + u16 cmd, old_cmd; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + old_cmd = cmd; + + if (bus->resource[0]->flags & IORESOURCE_IO) { + cmd |= PCI_COMMAND_IO; + } + if ((bus->resource[1]->flags & IORESOURCE_MEM) || + (bus->resource[2]->flags & IORESOURCE_PREFETCH)) { + cmd |= PCI_COMMAND_MEMORY; + } + + if (cmd != old_cmd) { + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + + printk("PCI bridge %s, command register -> %04x\n", + pci_name(dev), cmd); + +} + + + +int pcibios_enable_device(struct pci_dev *dev, int mask) +{ + u16 cmd, old_cmd; + int idx; + struct resource *r; + + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_PCI) { + pcibios_enable_bridge(dev); + } + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + old_cmd = cmd; + for(idx=0; idx<6; idx++) { + if (!(mask & (1 << idx))) + continue; + r = &dev->resource[idx]; + if (!r->start && r->end) { + printk(KERN_ERR "PCI: Device %s not available because of resource collisions\n", pci_name(dev)); + return -EINVAL; + } + if (r->flags & IORESOURCE_IO) + cmd |= PCI_COMMAND_IO; + if (r->flags & IORESOURCE_MEM) + cmd |= PCI_COMMAND_MEMORY; + } + if (dev->resource[PCI_ROM_RESOURCE].start) + cmd |= PCI_COMMAND_MEMORY; + if (cmd != old_cmd) { + printk(KERN_INFO "PCI: Enabling device %s (%04x -> %04x)\n", pci_name(dev), old_cmd, cmd); + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + return 0; +} + +/* + * If we set up a device for bus mastering, we need to check and set + * the latency timer as it may not be properly set. + */ +unsigned int pcibios_max_latency = 255; + +void pcibios_set_master(struct pci_dev *dev) +{ + u8 lat; + pci_read_config_byte(dev, PCI_LATENCY_TIMER, &lat); + if (lat < 16) + lat = (64 <= pcibios_max_latency) ? 64 : pcibios_max_latency; + else if (lat > pcibios_max_latency) + lat = pcibios_max_latency; + else + return; + printk(KERN_INFO "PCI: Setting latency timer of device %s to %d\n", pci_name(dev), lat); + pci_write_config_byte(dev, PCI_LATENCY_TIMER, lat); +} + +void __init pcibios_update_irq(struct pci_dev *dev, int irq) +{ + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); +} diff --git a/arch/sh64/kernel/process.c b/arch/sh64/kernel/process.c new file mode 100644 index 00000000000..efde41c0cd6 --- /dev/null +++ b/arch/sh64/kernel/process.c @@ -0,0 +1,962 @@ +/* + * 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. + * + * arch/sh64/kernel/process.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003 Paul Mundt + * Copyright (C) 2003, 2004 Richard Curnow + * + * Started from SH3/4 version: + * Copyright (C) 1999, 2000 Niibe Yutaka & Kaz Kojima + * + * In turn started from i386 version: + * Copyright (C) 1995 Linus Torvalds + * + */ + +/* + * This file handles the architecture-dependent parts of process handling.. + */ + +/* Temporary flags/tests. All to be removed/undefined. BEGIN */ +#define IDLE_TRACE +#define VM_SHOW_TABLES +#define VM_TEST_FAULT +#define VM_TEST_RTLBMISS +#define VM_TEST_WTLBMISS + +#undef VM_SHOW_TABLES +#undef IDLE_TRACE +/* Temporary flags/tests. All to be removed/undefined. END */ + +#define __KERNEL_SYSCALLS__ +#include <stdarg.h> + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/rwsem.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/interrupt.h> +#include <linux/unistd.h> +#include <linux/delay.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/processor.h> /* includes also <asm/registers.h> */ +#include <asm/mmu_context.h> +#include <asm/elf.h> +#include <asm/page.h> + +#include <linux/irq.h> + +struct task_struct *last_task_used_math = NULL; + +#ifdef IDLE_TRACE +#ifdef VM_SHOW_TABLES +/* For testing */ +static void print_PTE(long base) +{ + int i, skip=0; + long long x, y, *p = (long long *) base; + + for (i=0; i< 512; i++, p++){ + if (*p == 0) { + if (!skip) { + skip++; + printk("(0s) "); + } + } else { + skip=0; + x = (*p) >> 32; + y = (*p) & 0xffffffff; + printk("%08Lx%08Lx ", x, y); + if (!((i+1)&0x3)) printk("\n"); + } + } +} + +/* For testing */ +static void print_DIR(long base) +{ + int i, skip=0; + long *p = (long *) base; + + for (i=0; i< 512; i++, p++){ + if (*p == 0) { + if (!skip) { + skip++; + printk("(0s) "); + } + } else { + skip=0; + printk("%08lx ", *p); + if (!((i+1)&0x7)) printk("\n"); + } + } +} + +/* For testing */ +static void print_vmalloc_first_tables(void) +{ + +#define PRESENT 0x800 /* Bit 11 */ + + /* + * Do it really dirty by looking at raw addresses, + * raw offsets, no types. If we used pgtable/pgalloc + * macros/definitions we could hide potential bugs. + * + * Note that pointers are 32-bit for CDC. + */ + long pgdt, pmdt, ptet; + + pgdt = (long) &swapper_pg_dir; + printk("-->PGD (0x%08lx):\n", pgdt); + print_DIR(pgdt); + printk("\n"); + + /* VMALLOC pool is mapped at 0xc0000000, second (pointer) entry in PGD */ + pgdt += 4; + pmdt = (long) (* (long *) pgdt); + if (!(pmdt & PRESENT)) { + printk("No PMD\n"); + return; + } else pmdt &= 0xfffff000; + + printk("-->PMD (0x%08lx):\n", pmdt); + print_DIR(pmdt); + printk("\n"); + + /* Get the pmdt displacement for 0xc0000000 */ + pmdt += 2048; + + /* just look at first two address ranges ... */ + /* ... 0xc0000000 ... */ + ptet = (long) (* (long *) pmdt); + if (!(ptet & PRESENT)) { + printk("No PTE0\n"); + return; + } else ptet &= 0xfffff000; + + printk("-->PTE0 (0x%08lx):\n", ptet); + print_PTE(ptet); + printk("\n"); + + /* ... 0xc0001000 ... */ + ptet += 4; + if (!(ptet & PRESENT)) { + printk("No PTE1\n"); + return; + } else ptet &= 0xfffff000; + printk("-->PTE1 (0x%08lx):\n", ptet); + print_PTE(ptet); + printk("\n"); +} +#else +#define print_vmalloc_first_tables() +#endif /* VM_SHOW_TABLES */ + +static void test_VM(void) +{ + void *a, *b, *c; + +#ifdef VM_SHOW_TABLES + printk("Initial PGD/PMD/PTE\n"); +#endif + print_vmalloc_first_tables(); + + printk("Allocating 2 bytes\n"); + a = vmalloc(2); + print_vmalloc_first_tables(); + + printk("Allocating 4100 bytes\n"); + b = vmalloc(4100); + print_vmalloc_first_tables(); + + printk("Allocating 20234 bytes\n"); + c = vmalloc(20234); + print_vmalloc_first_tables(); + +#ifdef VM_TEST_FAULT + /* Here you may want to fault ! */ + +#ifdef VM_TEST_RTLBMISS + printk("Ready to fault upon read.\n"); + if (* (char *) a) { + printk("RTLBMISSed on area a !\n"); + } + printk("RTLBMISSed on area a !\n"); +#endif + +#ifdef VM_TEST_WTLBMISS + printk("Ready to fault upon write.\n"); + *((char *) b) = 'L'; + printk("WTLBMISSed on area b !\n"); +#endif + +#endif /* VM_TEST_FAULT */ + + printk("Deallocating the 4100 byte chunk\n"); + vfree(b); + print_vmalloc_first_tables(); + + printk("Deallocating the 2 byte chunk\n"); + vfree(a); + print_vmalloc_first_tables(); + + printk("Deallocating the last chunk\n"); + vfree(c); + print_vmalloc_first_tables(); +} + +extern unsigned long volatile jiffies; +int once = 0; +unsigned long old_jiffies; +int pid = -1, pgid = -1; + +void idle_trace(void) +{ + + _syscall0(int, getpid) + _syscall1(int, getpgid, int, pid) + + if (!once) { + /* VM allocation/deallocation simple test */ + test_VM(); + pid = getpid(); + + printk("Got all through to Idle !!\n"); + printk("I'm now going to loop forever ...\n"); + printk("Any ! below is a timer tick.\n"); + printk("Any . below is a getpgid system call from pid = %d.\n", pid); + + + old_jiffies = jiffies; + once++; + } + + if (old_jiffies != jiffies) { + old_jiffies = jiffies - old_jiffies; + switch (old_jiffies) { + case 1: + printk("!"); + break; + case 2: + printk("!!"); + break; + case 3: + printk("!!!"); + break; + case 4: + printk("!!!!"); + break; + default: + printk("(%d!)", (int) old_jiffies); + } + old_jiffies = jiffies; + } + pgid = getpgid(pid); + printk("."); +} +#else +#define idle_trace() do { } while (0) +#endif /* IDLE_TRACE */ + +static int hlt_counter = 1; + +#define HARD_IDLE_TIMEOUT (HZ / 3) + +void disable_hlt(void) +{ + hlt_counter++; +} + +void enable_hlt(void) +{ + hlt_counter--; +} + +static int __init nohlt_setup(char *__unused) +{ + hlt_counter = 1; + return 1; +} + +static int __init hlt_setup(char *__unused) +{ + hlt_counter = 0; + return 1; +} + +__setup("nohlt", nohlt_setup); +__setup("hlt", hlt_setup); + +static inline void hlt(void) +{ + if (hlt_counter) + return; + + __asm__ __volatile__ ("sleep" : : : "memory"); +} + +/* + * The idle loop on a uniprocessor SH.. + */ +void default_idle(void) +{ + /* endless idle loop with no priority at all */ + while (1) { + if (hlt_counter) { + while (1) + if (need_resched()) + break; + } else { + local_irq_disable(); + while (!need_resched()) { + local_irq_enable(); + idle_trace(); + hlt(); + local_irq_disable(); + } + local_irq_enable(); + } + schedule(); + } +} + +void cpu_idle(void) +{ + default_idle(); +} + +void machine_restart(char * __unused) +{ + extern void phys_stext(void); + + phys_stext(); +} + +void machine_halt(void) +{ + for (;;); +} + +void machine_power_off(void) +{ + extern void enter_deep_standby(void); + + enter_deep_standby(); +} + +void show_regs(struct pt_regs * regs) +{ + unsigned long long ah, al, bh, bl, ch, cl; + + printk("\n"); + + ah = (regs->pc) >> 32; + al = (regs->pc) & 0xffffffff; + bh = (regs->regs[18]) >> 32; + bl = (regs->regs[18]) & 0xffffffff; + ch = (regs->regs[15]) >> 32; + cl = (regs->regs[15]) & 0xffffffff; + printk("PC : %08Lx%08Lx LINK: %08Lx%08Lx SP : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->sr) >> 32; + al = (regs->sr) & 0xffffffff; + asm volatile ("getcon " __TEA ", %0" : "=r" (bh)); + asm volatile ("getcon " __TEA ", %0" : "=r" (bl)); + bh = (bh) >> 32; + bl = (bl) & 0xffffffff; + asm volatile ("getcon " __KCR0 ", %0" : "=r" (ch)); + asm volatile ("getcon " __KCR0 ", %0" : "=r" (cl)); + ch = (ch) >> 32; + cl = (cl) & 0xffffffff; + printk("SR : %08Lx%08Lx TEA : %08Lx%08Lx KCR0: %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[0]) >> 32; + al = (regs->regs[0]) & 0xffffffff; + bh = (regs->regs[1]) >> 32; + bl = (regs->regs[1]) & 0xffffffff; + ch = (regs->regs[2]) >> 32; + cl = (regs->regs[2]) & 0xffffffff; + printk("R0 : %08Lx%08Lx R1 : %08Lx%08Lx R2 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[3]) >> 32; + al = (regs->regs[3]) & 0xffffffff; + bh = (regs->regs[4]) >> 32; + bl = (regs->regs[4]) & 0xffffffff; + ch = (regs->regs[5]) >> 32; + cl = (regs->regs[5]) & 0xffffffff; + printk("R3 : %08Lx%08Lx R4 : %08Lx%08Lx R5 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[6]) >> 32; + al = (regs->regs[6]) & 0xffffffff; + bh = (regs->regs[7]) >> 32; + bl = (regs->regs[7]) & 0xffffffff; + ch = (regs->regs[8]) >> 32; + cl = (regs->regs[8]) & 0xffffffff; + printk("R6 : %08Lx%08Lx R7 : %08Lx%08Lx R8 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[9]) >> 32; + al = (regs->regs[9]) & 0xffffffff; + bh = (regs->regs[10]) >> 32; + bl = (regs->regs[10]) & 0xffffffff; + ch = (regs->regs[11]) >> 32; + cl = (regs->regs[11]) & 0xffffffff; + printk("R9 : %08Lx%08Lx R10 : %08Lx%08Lx R11 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[12]) >> 32; + al = (regs->regs[12]) & 0xffffffff; + bh = (regs->regs[13]) >> 32; + bl = (regs->regs[13]) & 0xffffffff; + ch = (regs->regs[14]) >> 32; + cl = (regs->regs[14]) & 0xffffffff; + printk("R12 : %08Lx%08Lx R13 : %08Lx%08Lx R14 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[16]) >> 32; + al = (regs->regs[16]) & 0xffffffff; + bh = (regs->regs[17]) >> 32; + bl = (regs->regs[17]) & 0xffffffff; + ch = (regs->regs[19]) >> 32; + cl = (regs->regs[19]) & 0xffffffff; + printk("R16 : %08Lx%08Lx R17 : %08Lx%08Lx R19 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[20]) >> 32; + al = (regs->regs[20]) & 0xffffffff; + bh = (regs->regs[21]) >> 32; + bl = (regs->regs[21]) & 0xffffffff; + ch = (regs->regs[22]) >> 32; + cl = (regs->regs[22]) & 0xffffffff; + printk("R20 : %08Lx%08Lx R21 : %08Lx%08Lx R22 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[23]) >> 32; + al = (regs->regs[23]) & 0xffffffff; + bh = (regs->regs[24]) >> 32; + bl = (regs->regs[24]) & 0xffffffff; + ch = (regs->regs[25]) >> 32; + cl = (regs->regs[25]) & 0xffffffff; + printk("R23 : %08Lx%08Lx R24 : %08Lx%08Lx R25 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[26]) >> 32; + al = (regs->regs[26]) & 0xffffffff; + bh = (regs->regs[27]) >> 32; + bl = (regs->regs[27]) & 0xffffffff; + ch = (regs->regs[28]) >> 32; + cl = (regs->regs[28]) & 0xffffffff; + printk("R26 : %08Lx%08Lx R27 : %08Lx%08Lx R28 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[29]) >> 32; + al = (regs->regs[29]) & 0xffffffff; + bh = (regs->regs[30]) >> 32; + bl = (regs->regs[30]) & 0xffffffff; + ch = (regs->regs[31]) >> 32; + cl = (regs->regs[31]) & 0xffffffff; + printk("R29 : %08Lx%08Lx R30 : %08Lx%08Lx R31 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[32]) >> 32; + al = (regs->regs[32]) & 0xffffffff; + bh = (regs->regs[33]) >> 32; + bl = (regs->regs[33]) & 0xffffffff; + ch = (regs->regs[34]) >> 32; + cl = (regs->regs[34]) & 0xffffffff; + printk("R32 : %08Lx%08Lx R33 : %08Lx%08Lx R34 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[35]) >> 32; + al = (regs->regs[35]) & 0xffffffff; + bh = (regs->regs[36]) >> 32; + bl = (regs->regs[36]) & 0xffffffff; + ch = (regs->regs[37]) >> 32; + cl = (regs->regs[37]) & 0xffffffff; + printk("R35 : %08Lx%08Lx R36 : %08Lx%08Lx R37 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[38]) >> 32; + al = (regs->regs[38]) & 0xffffffff; + bh = (regs->regs[39]) >> 32; + bl = (regs->regs[39]) & 0xffffffff; + ch = (regs->regs[40]) >> 32; + cl = (regs->regs[40]) & 0xffffffff; + printk("R38 : %08Lx%08Lx R39 : %08Lx%08Lx R40 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[41]) >> 32; + al = (regs->regs[41]) & 0xffffffff; + bh = (regs->regs[42]) >> 32; + bl = (regs->regs[42]) & 0xffffffff; + ch = (regs->regs[43]) >> 32; + cl = (regs->regs[43]) & 0xffffffff; + printk("R41 : %08Lx%08Lx R42 : %08Lx%08Lx R43 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[44]) >> 32; + al = (regs->regs[44]) & 0xffffffff; + bh = (regs->regs[45]) >> 32; + bl = (regs->regs[45]) & 0xffffffff; + ch = (regs->regs[46]) >> 32; + cl = (regs->regs[46]) & 0xffffffff; + printk("R44 : %08Lx%08Lx R45 : %08Lx%08Lx R46 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[47]) >> 32; + al = (regs->regs[47]) & 0xffffffff; + bh = (regs->regs[48]) >> 32; + bl = (regs->regs[48]) & 0xffffffff; + ch = (regs->regs[49]) >> 32; + cl = (regs->regs[49]) & 0xffffffff; + printk("R47 : %08Lx%08Lx R48 : %08Lx%08Lx R49 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[50]) >> 32; + al = (regs->regs[50]) & 0xffffffff; + bh = (regs->regs[51]) >> 32; + bl = (regs->regs[51]) & 0xffffffff; + ch = (regs->regs[52]) >> 32; + cl = (regs->regs[52]) & 0xffffffff; + printk("R50 : %08Lx%08Lx R51 : %08Lx%08Lx R52 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[53]) >> 32; + al = (regs->regs[53]) & 0xffffffff; + bh = (regs->regs[54]) >> 32; + bl = (regs->regs[54]) & 0xffffffff; + ch = (regs->regs[55]) >> 32; + cl = (regs->regs[55]) & 0xffffffff; + printk("R53 : %08Lx%08Lx R54 : %08Lx%08Lx R55 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[56]) >> 32; + al = (regs->regs[56]) & 0xffffffff; + bh = (regs->regs[57]) >> 32; + bl = (regs->regs[57]) & 0xffffffff; + ch = (regs->regs[58]) >> 32; + cl = (regs->regs[58]) & 0xffffffff; + printk("R56 : %08Lx%08Lx R57 : %08Lx%08Lx R58 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[59]) >> 32; + al = (regs->regs[59]) & 0xffffffff; + bh = (regs->regs[60]) >> 32; + bl = (regs->regs[60]) & 0xffffffff; + ch = (regs->regs[61]) >> 32; + cl = (regs->regs[61]) & 0xffffffff; + printk("R59 : %08Lx%08Lx R60 : %08Lx%08Lx R61 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->regs[62]) >> 32; + al = (regs->regs[62]) & 0xffffffff; + bh = (regs->tregs[0]) >> 32; + bl = (regs->tregs[0]) & 0xffffffff; + ch = (regs->tregs[1]) >> 32; + cl = (regs->tregs[1]) & 0xffffffff; + printk("R62 : %08Lx%08Lx T0 : %08Lx%08Lx T1 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->tregs[2]) >> 32; + al = (regs->tregs[2]) & 0xffffffff; + bh = (regs->tregs[3]) >> 32; + bl = (regs->tregs[3]) & 0xffffffff; + ch = (regs->tregs[4]) >> 32; + cl = (regs->tregs[4]) & 0xffffffff; + printk("T2 : %08Lx%08Lx T3 : %08Lx%08Lx T4 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + ah = (regs->tregs[5]) >> 32; + al = (regs->tregs[5]) & 0xffffffff; + bh = (regs->tregs[6]) >> 32; + bl = (regs->tregs[6]) & 0xffffffff; + ch = (regs->tregs[7]) >> 32; + cl = (regs->tregs[7]) & 0xffffffff; + printk("T5 : %08Lx%08Lx T6 : %08Lx%08Lx T7 : %08Lx%08Lx\n", + ah, al, bh, bl, ch, cl); + + /* + * If we're in kernel mode, dump the stack too.. + */ + if (!user_mode(regs)) { + void show_stack(struct task_struct *tsk, unsigned long *sp); + unsigned long sp = regs->regs[15] & 0xffffffff; + struct task_struct *tsk = get_current(); + + tsk->thread.kregs = regs; + + show_stack(tsk, (unsigned long *)sp); + } +} + +struct task_struct * alloc_task_struct(void) +{ + /* Get task descriptor pages */ + return (struct task_struct *) + __get_free_pages(GFP_KERNEL, get_order(THREAD_SIZE)); +} + +void free_task_struct(struct task_struct *p) +{ + free_pages((unsigned long) p, get_order(THREAD_SIZE)); +} + +/* + * Create a kernel thread + */ + +/* + * This is the mechanism for creating a new kernel thread. + * + * NOTE! Only a kernel-only process(ie the swapper or direct descendants + * who haven't done an "execve()") should use this: it will work within + * a system call from a "real" process, but the process memory space will + * not be free'd until both the parent and the child have exited. + */ +int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) +{ + /* A bit less processor dependent than older sh ... */ + unsigned int reply; + +static __inline__ _syscall2(int,clone,unsigned long,flags,unsigned long,newsp) +static __inline__ _syscall1(int,exit,int,ret) + + reply = clone(flags | CLONE_VM, 0); + if (!reply) { + /* Child */ + reply = exit(fn(arg)); + } + + return reply; +} + +/* + * Free current thread data structures etc.. + */ +void exit_thread(void) +{ + /* See arch/sparc/kernel/process.c for the precedent for doing this -- RPC. + + The SH-5 FPU save/restore approach relies on last_task_used_math + pointing to a live task_struct. When another task tries to use the + FPU for the 1st time, the FPUDIS trap handling (see + arch/sh64/kernel/fpu.c) will save the existing FPU state to the + FP regs field within last_task_used_math before re-loading the new + task's FPU state (or initialising it if the FPU has been used + before). So if last_task_used_math is stale, and its page has already been + re-allocated for another use, the consequences are rather grim. Unless we + null it here, there is no other path through which it would get safely + nulled. */ + +#ifdef CONFIG_SH_FPU + if (last_task_used_math == current) { + last_task_used_math = NULL; + } +#endif +} + +void flush_thread(void) +{ + + /* Called by fs/exec.c (flush_old_exec) to remove traces of a + * previously running executable. */ +#ifdef CONFIG_SH_FPU + if (last_task_used_math == current) { + last_task_used_math = NULL; + } + /* Force FPU state to be reinitialised after exec */ + clear_used_math(); +#endif + + /* if we are a kernel thread, about to change to user thread, + * update kreg + */ + if(current->thread.kregs==&fake_swapper_regs) { + current->thread.kregs = + ((struct pt_regs *)(THREAD_SIZE + (unsigned long) current) - 1); + current->thread.uregs = current->thread.kregs; + } +} + +void release_thread(struct task_struct *dead_task) +{ + /* do nothing */ +} + +/* Fill in the fpu structure for a core dump.. */ +int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpu) +{ +#ifdef CONFIG_SH_FPU + int fpvalid; + struct task_struct *tsk = current; + + fpvalid = !!tsk_used_math(tsk); + if (fpvalid) { + if (current == last_task_used_math) { + grab_fpu(); + fpsave(&tsk->thread.fpu.hard); + release_fpu(); + last_task_used_math = 0; + regs->sr |= SR_FD; + } + + memcpy(fpu, &tsk->thread.fpu.hard, sizeof(*fpu)); + } + + return fpvalid; +#else + return 0; /* Task didn't use the fpu at all. */ +#endif +} + +asmlinkage void ret_from_fork(void); + +int copy_thread(int nr, unsigned long clone_flags, unsigned long usp, + unsigned long unused, + struct task_struct *p, struct pt_regs *regs) +{ + struct pt_regs *childregs; + unsigned long long se; /* Sign extension */ + +#ifdef CONFIG_SH_FPU + if(last_task_used_math == current) { + grab_fpu(); + fpsave(¤t->thread.fpu.hard); + release_fpu(); + last_task_used_math = NULL; + regs->sr |= SR_FD; + } +#endif + /* Copy from sh version */ + childregs = ((struct pt_regs *)(THREAD_SIZE + (unsigned long) p->thread_info )) - 1; + + *childregs = *regs; + + if (user_mode(regs)) { + childregs->regs[15] = usp; + p->thread.uregs = childregs; + } else { + childregs->regs[15] = (unsigned long)p->thread_info + THREAD_SIZE; + } + + childregs->regs[9] = 0; /* Set return value for child */ + childregs->sr |= SR_FD; /* Invalidate FPU flag */ + + p->thread.sp = (unsigned long) childregs; + p->thread.pc = (unsigned long) ret_from_fork; + + /* + * Sign extend the edited stack. + * Note that thread.pc and thread.pc will stay + * 32-bit wide and context switch must take care + * of NEFF sign extension. + */ + + se = childregs->regs[15]; + se = (se & NEFF_SIGN) ? (se | NEFF_MASK) : se; + childregs->regs[15] = se; + + return 0; +} + +/* + * fill in the user structure for a core dump.. + */ +void dump_thread(struct pt_regs * regs, struct user * dump) +{ + dump->magic = CMAGIC; + dump->start_code = current->mm->start_code; + dump->start_data = current->mm->start_data; + dump->start_stack = regs->regs[15] & ~(PAGE_SIZE - 1); + dump->u_tsize = (current->mm->end_code - dump->start_code) >> PAGE_SHIFT; + dump->u_dsize = (current->mm->brk + (PAGE_SIZE-1) - dump->start_data) >> PAGE_SHIFT; + dump->u_ssize = (current->mm->start_stack - dump->start_stack + + PAGE_SIZE - 1) >> PAGE_SHIFT; + /* Debug registers will come here. */ + + dump->regs = *regs; + + dump->u_fpvalid = dump_fpu(regs, &dump->fpu); +} + +asmlinkage int sys_fork(unsigned long r2, unsigned long r3, + unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs *pregs) +{ + return do_fork(SIGCHLD, pregs->regs[15], pregs, 0, 0, 0); +} + +asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp, + unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs *pregs) +{ + if (!newsp) + newsp = pregs->regs[15]; + return do_fork(clone_flags, newsp, pregs, 0, 0, 0); +} + +/* + * This is trivial, and on the face of it looks like it + * could equally well be done in user mode. + * + * Not so, for quite unobvious reasons - register pressure. + * In user mode vfork() cannot have a stack frame, and if + * done by calling the "clone()" system call directly, you + * do not have enough call-clobbered registers to hold all + * the information you need. + */ +asmlinkage int sys_vfork(unsigned long r2, unsigned long r3, + unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs *pregs) +{ + return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, pregs->regs[15], pregs, 0, 0, 0); +} + +/* + * sys_execve() executes a new program. + */ +asmlinkage int sys_execve(char *ufilename, char **uargv, + char **uenvp, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs *pregs) +{ + int error; + char *filename; + + lock_kernel(); + filename = getname((char __user *)ufilename); + error = PTR_ERR(filename); + if (IS_ERR(filename)) + goto out; + + error = do_execve(filename, + (char __user * __user *)uargv, + (char __user * __user *)uenvp, + pregs); + if (error == 0) { + task_lock(current); + current->ptrace &= ~PT_DTRACE; + task_unlock(current); + } + putname(filename); +out: + unlock_kernel(); + return error; +} + +/* + * These bracket the sleeping functions.. + */ +extern void interruptible_sleep_on(wait_queue_head_t *q); + +#define mid_sched ((unsigned long) interruptible_sleep_on) + +static int in_sh64_switch_to(unsigned long pc) +{ + extern char __sh64_switch_to_end; + /* For a sleeping task, the PC is somewhere in the middle of the function, + so we don't have to worry about masking the LSB off */ + return (pc >= (unsigned long) sh64_switch_to) && + (pc < (unsigned long) &__sh64_switch_to_end); +} + +unsigned long get_wchan(struct task_struct *p) +{ + unsigned long schedule_fp; + unsigned long sh64_switch_to_fp; + unsigned long schedule_caller_pc; + unsigned long pc; + + if (!p || p == current || p->state == TASK_RUNNING) + return 0; + + /* + * The same comment as on the Alpha applies here, too ... + */ + pc = thread_saved_pc(p); + +#ifdef CONFIG_FRAME_POINTER + if (in_sh64_switch_to(pc)) { + sh64_switch_to_fp = (long) p->thread.sp; + /* r14 is saved at offset 4 in the sh64_switch_to frame */ + schedule_fp = *(unsigned long *) (long)(sh64_switch_to_fp + 4); + + /* and the caller of 'schedule' is (currently!) saved at offset 24 + in the frame of schedule (from disasm) */ + schedule_caller_pc = *(unsigned long *) (long)(schedule_fp + 24); + return schedule_caller_pc; + } +#endif + return pc; +} + +/* Provide a /proc/asids file that lists out the + ASIDs currently associated with the processes. (If the DM.PC register is + examined through the debug link, this shows ASID + PC. To make use of this, + the PID->ASID relationship needs to be known. This is primarily for + debugging.) + */ + +#if defined(CONFIG_SH64_PROC_ASIDS) +#include <linux/init.h> +#include <linux/proc_fs.h> + +static int +asids_proc_info(char *buf, char **start, off_t fpos, int length, int *eof, void *data) +{ + int len=0; + struct task_struct *p; + read_lock(&tasklist_lock); + for_each_process(p) { + int pid = p->pid; + struct mm_struct *mm; + if (!pid) continue; + mm = p->mm; + if (mm) { + unsigned long asid, context; + context = mm->context; + asid = (context & 0xff); + len += sprintf(buf+len, "%5d : %02lx\n", pid, asid); + } else { + len += sprintf(buf+len, "%5d : (none)\n", pid); + } + } + read_unlock(&tasklist_lock); + *eof = 1; + return len; +} + +static int __init register_proc_asids(void) +{ + create_proc_read_entry("asids", 0, NULL, asids_proc_info, NULL); + return 0; +} + +__initcall(register_proc_asids); +#endif + diff --git a/arch/sh64/kernel/ptrace.c b/arch/sh64/kernel/ptrace.c new file mode 100644 index 00000000000..800288c1562 --- /dev/null +++ b/arch/sh64/kernel/ptrace.c @@ -0,0 +1,376 @@ +/* + * 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. + * + * arch/sh64/kernel/ptrace.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003 Paul Mundt + * + * Started from SH3/4 version: + * SuperH version: Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka + * + * Original x86 implementation: + * By Ross Biro 1/23/92 + * edited by Linus Torvalds + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/user.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/system.h> +#include <asm/processor.h> +#include <asm/mmu_context.h> + +/* This mask defines the bits of the SR which the user is not allowed to + change, which are everything except S, Q, M, PR, SZ, FR. */ +#define SR_MASK (0xffff8cfd) + +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + +/* + * This routine will get a word from the user area in the process kernel stack. + */ +static inline int get_stack_long(struct task_struct *task, int offset) +{ + unsigned char *stack; + + stack = (unsigned char *)(task->thread.uregs); + stack += offset; + return (*((int *)stack)); +} + +static inline unsigned long +get_fpu_long(struct task_struct *task, unsigned long addr) +{ + unsigned long tmp; + struct pt_regs *regs; + regs = (struct pt_regs*)((unsigned char *)task + THREAD_SIZE) - 1; + + if (!tsk_used_math(task)) { + if (addr == offsetof(struct user_fpu_struct, fpscr)) { + tmp = FPSCR_INIT; + } else { + tmp = 0xffffffffUL; /* matches initial value in fpu.c */ + } + return tmp; + } + + if (last_task_used_math == task) { + grab_fpu(); + fpsave(&task->thread.fpu.hard); + release_fpu(); + last_task_used_math = 0; + regs->sr |= SR_FD; + } + + tmp = ((long *)&task->thread.fpu)[addr / sizeof(unsigned long)]; + return tmp; +} + +/* + * This routine will put a word into the user area in the process kernel stack. + */ +static inline int put_stack_long(struct task_struct *task, int offset, + unsigned long data) +{ + unsigned char *stack; + + stack = (unsigned char *)(task->thread.uregs); + stack += offset; + *(unsigned long *) stack = data; + return 0; +} + +static inline int +put_fpu_long(struct task_struct *task, unsigned long addr, unsigned long data) +{ + struct pt_regs *regs; + + regs = (struct pt_regs*)((unsigned char *)task + THREAD_SIZE) - 1; + + if (!tsk_used_math(task)) { + fpinit(&task->thread.fpu.hard); + set_stopped_child_used_math(task); + } else if (last_task_used_math == task) { + grab_fpu(); + fpsave(&task->thread.fpu.hard); + release_fpu(); + last_task_used_math = 0; + regs->sr |= SR_FD; + } + + ((long *)&task->thread.fpu)[addr / sizeof(unsigned long)] = data; + return 0; +} + +asmlinkage int sys_ptrace(long request, long pid, long addr, long data) +{ + struct task_struct *child; + extern void poke_real_address_q(unsigned long long addr, unsigned long long data); +#define WPC_DBRMODE 0x0d104008 + static int first_call = 1; + int ret; + + lock_kernel(); + + if (first_call) { + /* Set WPC.DBRMODE to 0. This makes all debug events get + * delivered through RESVEC, i.e. into the handlers in entry.S. + * (If the kernel was downloaded using a remote gdb, WPC.DBRMODE + * would normally be left set to 1, which makes debug events get + * delivered through DBRVEC, i.e. into the remote gdb's + * handlers. This prevents ptrace getting them, and confuses + * the remote gdb.) */ + printk("DBRMODE set to 0 to permit native debugging\n"); + poke_real_address_q(WPC_DBRMODE, 0); + first_call = 0; + } + + ret = -EPERM; + if (request == PTRACE_TRACEME) { + /* are we already being traced? */ + if (current->ptrace & PT_PTRACED) + goto out; + /* set the ptrace bit in the process flags. */ + current->ptrace |= PT_PTRACED; + ret = 0; + goto out; + } + ret = -ESRCH; + read_lock(&tasklist_lock); + child = find_task_by_pid(pid); + if (child) + get_task_struct(child); + read_unlock(&tasklist_lock); + if (!child) + goto out; + + ret = -EPERM; + if (pid == 1) /* you may not mess with init */ + goto out_tsk; + + if (request == PTRACE_ATTACH) { + ret = ptrace_attach(child); + goto out_tsk; + } + + ret = ptrace_check_attach(child, request == PTRACE_KILL); + if (ret < 0) + goto out_tsk; + + switch (request) { + /* when I and D space are separate, these will need to be fixed. */ + case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKDATA: { + unsigned long tmp; + int copied; + + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + ret = -EIO; + if (copied != sizeof(tmp)) + break; + ret = put_user(tmp,(unsigned long *) data); + break; + } + + /* read the word at location addr in the USER area. */ + case PTRACE_PEEKUSR: { + unsigned long tmp; + + ret = -EIO; + if ((addr & 3) || addr < 0) + break; + + if (addr < sizeof(struct pt_regs)) + tmp = get_stack_long(child, addr); + else if ((addr >= offsetof(struct user, fpu)) && + (addr < offsetof(struct user, u_fpvalid))) { + tmp = get_fpu_long(child, addr - offsetof(struct user, fpu)); + } else if (addr == offsetof(struct user, u_fpvalid)) { + tmp = !!tsk_used_math(child); + } else { + break; + } + ret = put_user(tmp, (unsigned long *)data); + break; + } + + /* when I and D space are separate, this will have to be fixed. */ + case PTRACE_POKETEXT: /* write the word at location addr. */ + case PTRACE_POKEDATA: + ret = 0; + if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data)) + break; + ret = -EIO; + break; + + case PTRACE_POKEUSR: + /* write the word at location addr in the USER area. We must + disallow any changes to certain SR bits or u_fpvalid, since + this could crash the kernel or result in a security + loophole. */ + ret = -EIO; + if ((addr & 3) || addr < 0) + break; + + if (addr < sizeof(struct pt_regs)) { + /* Ignore change of top 32 bits of SR */ + if (addr == offsetof (struct pt_regs, sr)+4) + { + ret = 0; + break; + } + /* If lower 32 bits of SR, ignore non-user bits */ + if (addr == offsetof (struct pt_regs, sr)) + { + long cursr = get_stack_long(child, addr); + data &= ~(SR_MASK); + data |= (cursr & SR_MASK); + } + ret = put_stack_long(child, addr, data); + } + else if ((addr >= offsetof(struct user, fpu)) && + (addr < offsetof(struct user, u_fpvalid))) { + ret = put_fpu_long(child, addr - offsetof(struct user, fpu), data); + } + break; + + case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ + case PTRACE_CONT: { /* restart after signal. */ + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + if (request == PTRACE_SYSCALL) + set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + else + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + child->exit_code = data; + wake_up_process(child); + ret = 0; + break; + } + +/* + * make the child exit. Best I can do is send it a sigkill. + * perhaps it should be put in the status that it wants to + * exit. + */ + case PTRACE_KILL: { + ret = 0; + if (child->exit_state == EXIT_ZOMBIE) /* already dead */ + break; + child->exit_code = SIGKILL; + wake_up_process(child); + break; + } + + case PTRACE_SINGLESTEP: { /* set the trap flag. */ + struct pt_regs *regs; + + ret = -EIO; + if ((unsigned long) data > _NSIG) + break; + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + if ((child->ptrace & PT_DTRACE) == 0) { + /* Spurious delayed TF traps may occur */ + child->ptrace |= PT_DTRACE; + } + + regs = child->thread.uregs; + + regs->sr |= SR_SSTEP; /* auto-resetting upon exception */ + + child->exit_code = data; + /* give it a chance to run. */ + wake_up_process(child); + ret = 0; + break; + } + + case PTRACE_DETACH: /* detach a process that was attached. */ + ret = ptrace_detach(child, data); + break; + + default: + ret = ptrace_request(child, request, addr, data); + break; + } +out_tsk: + put_task_struct(child); +out: + unlock_kernel(); + return ret; +} + +asmlinkage void syscall_trace(void) +{ + struct task_struct *tsk = current; + + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + if (!(tsk->ptrace & PT_PTRACED)) + return; + + ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) + ? 0x80 : 0)); + /* + * this isn't the same as continuing with a signal, but it will do + * for normal use. strace only continues with a signal if the + * stopping signal is not SIGTRAP. -brl + */ + if (tsk->exit_code) { + send_sig(tsk->exit_code, tsk, 1); + tsk->exit_code = 0; + } +} + +/* Called with interrupts disabled */ +asmlinkage void do_single_step(unsigned long long vec, struct pt_regs *regs) +{ + /* This is called after a single step exception (DEBUGSS). + There is no need to change the PC, as it is a post-execution + exception, as entry.S does not do anything to the PC for DEBUGSS. + We need to clear the Single Step setting in SR to avoid + continually stepping. */ + local_irq_enable(); + regs->sr &= ~SR_SSTEP; + force_sig(SIGTRAP, current); +} + +/* Called with interrupts disabled */ +asmlinkage void do_software_break_point(unsigned long long vec, + struct pt_regs *regs) +{ + /* We need to forward step the PC, to counteract the backstep done + in signal.c. */ + local_irq_enable(); + force_sig(SIGTRAP, current); + regs->pc += 4; +} + +/* + * Called by kernel/ptrace.c when detaching.. + * + * Make sure single step bits etc are not set. + */ +void ptrace_disable(struct task_struct *child) +{ + /* nothing to do.. */ +} diff --git a/arch/sh64/kernel/semaphore.c b/arch/sh64/kernel/semaphore.c new file mode 100644 index 00000000000..72c16533436 --- /dev/null +++ b/arch/sh64/kernel/semaphore.c @@ -0,0 +1,140 @@ +/* + * Just taken from alpha implementation. + * This can't work well, perhaps. + */ +/* + * Generic semaphore code. Buyer beware. Do your own + * specific changes in <asm/semaphore-helper.h> + */ + +#include <linux/errno.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/init.h> +#include <asm/semaphore.h> +#include <asm/semaphore-helper.h> + +spinlock_t semaphore_wake_lock; + +/* + * Semaphores are implemented using a two-way counter: + * The "count" variable is decremented for each process + * that tries to sleep, while the "waking" variable is + * incremented when the "up()" code goes to wake up waiting + * processes. + * + * Notably, the inline "up()" and "down()" functions can + * efficiently test if they need to do any extra work (up + * needs to do something only if count was negative before + * the increment operation. + * + * waking_non_zero() (from asm/semaphore.h) must execute + * atomically. + * + * When __up() is called, the count was negative before + * incrementing it, and we need to wake up somebody. + * + * This routine adds one to the count of processes that need to + * wake up and exit. ALL waiting processes actually wake up but + * only the one that gets to the "waking" field first will gate + * through and acquire the semaphore. The others will go back + * to sleep. + * + * Note that these functions are only called when there is + * contention on the lock, and as such all this is the + * "non-critical" part of the whole semaphore business. The + * critical part is the inline stuff in <asm/semaphore.h> + * where we want to avoid any extra jumps and calls. + */ +void __up(struct semaphore *sem) +{ + wake_one_more(sem); + wake_up(&sem->wait); +} + +/* + * Perform the "down" function. Return zero for semaphore acquired, + * return negative for signalled out of the function. + * + * If called from __down, the return is ignored and the wait loop is + * not interruptible. This means that a task waiting on a semaphore + * using "down()" cannot be killed until someone does an "up()" on + * the semaphore. + * + * If called from __down_interruptible, the return value gets checked + * upon return. If the return value is negative then the task continues + * with the negative value in the return register (it can be tested by + * the caller). + * + * Either form may be used in conjunction with "up()". + * + */ + +#define DOWN_VAR \ + struct task_struct *tsk = current; \ + wait_queue_t wait; \ + init_waitqueue_entry(&wait, tsk); + +#define DOWN_HEAD(task_state) \ + \ + \ + tsk->state = (task_state); \ + add_wait_queue(&sem->wait, &wait); \ + \ + /* \ + * Ok, we're set up. sem->count is known to be less than zero \ + * so we must wait. \ + * \ + * We can let go the lock for purposes of waiting. \ + * We re-acquire it after awaking so as to protect \ + * all semaphore operations. \ + * \ + * If "up()" is called before we call waking_non_zero() then \ + * we will catch it right away. If it is called later then \ + * we will have to go through a wakeup cycle to catch it. \ + * \ + * Multiple waiters contend for the semaphore lock to see \ + * who gets to gate through and who has to wait some more. \ + */ \ + for (;;) { + +#define DOWN_TAIL(task_state) \ + tsk->state = (task_state); \ + } \ + tsk->state = TASK_RUNNING; \ + remove_wait_queue(&sem->wait, &wait); + +void __sched __down(struct semaphore * sem) +{ + DOWN_VAR + DOWN_HEAD(TASK_UNINTERRUPTIBLE) + if (waking_non_zero(sem)) + break; + schedule(); + DOWN_TAIL(TASK_UNINTERRUPTIBLE) +} + +int __sched __down_interruptible(struct semaphore * sem) +{ + int ret = 0; + DOWN_VAR + DOWN_HEAD(TASK_INTERRUPTIBLE) + + ret = waking_non_zero_interruptible(sem, tsk); + if (ret) + { + if (ret == 1) + /* ret != 0 only if we get interrupted -arca */ + ret = 0; + break; + } + schedule(); + DOWN_TAIL(TASK_INTERRUPTIBLE) + return ret; +} + +int __down_trylock(struct semaphore * sem) +{ + return waking_non_zero_trylock(sem); +} diff --git a/arch/sh64/kernel/setup.c b/arch/sh64/kernel/setup.c new file mode 100644 index 00000000000..c7a7b816a30 --- /dev/null +++ b/arch/sh64/kernel/setup.c @@ -0,0 +1,385 @@ +/* + * 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. + * + * arch/sh64/kernel/setup.c + * + * sh64 Arch Support + * + * This file handles the architecture-dependent parts of initialization + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003, 2004 Paul Mundt + * + * benedict.gaster@superh.com: 2nd May 2002 + * Modified to use the empty_zero_page to pass command line arguments. + * + * benedict.gaster@superh.com: 3rd May 2002 + * Added support for ramdisk, removing statically linked romfs at the same time. + * + * lethal@linux-sh.org: 15th May 2003 + * Added generic procfs cpuinfo reporting. Make boards just export their name. + * + * lethal@linux-sh.org: 25th May 2003 + * Added generic get_cpu_subtype() for subtype reporting from cpu_data->type. + * + */ +#include <linux/errno.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/stddef.h> +#include <linux/unistd.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/user.h> +#include <linux/a.out.h> +#include <linux/tty.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/seq_file.h> +#include <linux/blkdev.h> +#include <linux/bootmem.h> +#include <linux/console.h> +#include <linux/root_dev.h> +#include <linux/cpu.h> +#include <linux/initrd.h> +#include <asm/processor.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/platform.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/setup.h> +#include <asm/smp.h> + +#ifdef CONFIG_VT +#include <linux/console.h> +#endif + +struct screen_info screen_info; + +#ifdef CONFIG_BLK_DEV_RAM +extern int rd_doload; /* 1 = load ramdisk, 0 = don't load */ +extern int rd_prompt; /* 1 = prompt for ramdisk, 0 = don't prompt */ +extern int rd_image_start; /* starting block # of image */ +#endif + +extern int root_mountflags; +extern char *get_system_type(void); +extern void platform_setup(void); +extern void platform_monitor(void); +extern void platform_reserve(void); +extern int sh64_cache_init(void); +extern int sh64_tlb_init(void); + +#define RAMDISK_IMAGE_START_MASK 0x07FF +#define RAMDISK_PROMPT_FLAG 0x8000 +#define RAMDISK_LOAD_FLAG 0x4000 + +static char command_line[COMMAND_LINE_SIZE] = { 0, }; +unsigned long long memory_start = CONFIG_MEMORY_START; +unsigned long long memory_end = CONFIG_MEMORY_START + (CONFIG_MEMORY_SIZE_IN_MB * 1024 * 1024); + +struct sh_cpuinfo boot_cpu_data; + +static inline void parse_mem_cmdline (char ** cmdline_p) +{ + char c = ' ', *to = command_line, *from = COMMAND_LINE; + int len = 0; + + /* Save unparsed command line copy for /proc/cmdline */ + memcpy(saved_command_line, COMMAND_LINE, COMMAND_LINE_SIZE); + saved_command_line[COMMAND_LINE_SIZE-1] = '\0'; + + for (;;) { + /* + * "mem=XXX[kKmM]" defines a size of memory. + */ + if (c == ' ' && !memcmp(from, "mem=", 4)) { + if (to != command_line) + to--; + { + unsigned long mem_size; + + mem_size = memparse(from+4, &from); + memory_end = memory_start + mem_size; + } + } + c = *(from++); + if (!c) + break; + if (COMMAND_LINE_SIZE <= ++len) + break; + *(to++) = c; + } + *to = '\0'; + + *cmdline_p = command_line; +} + +static void __init sh64_cpu_type_detect(void) +{ + extern unsigned long long peek_real_address_q(unsigned long long addr); + 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; + } else { + boot_cpu_data.type = CPU_SH_NONE; + } +} + +void __init setup_arch(char **cmdline_p) +{ + unsigned long bootmap_size, i; + unsigned long first_pfn, start_pfn, last_pfn, pages; + +#ifdef CONFIG_EARLY_PRINTK + extern void enable_early_printk(void); + + /* + * Setup Early SCIF console + */ + enable_early_printk(); +#endif + + /* + * Setup TLB mappings + */ + sh64_tlb_init(); + + /* + * Caches are already initialized by the time we get here, so we just + * fill in cpu_data info for the caches. + */ + sh64_cache_init(); + + platform_setup(); + platform_monitor(); + + sh64_cpu_type_detect(); + + ROOT_DEV = old_decode_dev(ORIG_ROOT_DEV); + +#ifdef CONFIG_BLK_DEV_RAM + rd_image_start = RAMDISK_FLAGS & RAMDISK_IMAGE_START_MASK; + rd_prompt = ((RAMDISK_FLAGS & RAMDISK_PROMPT_FLAG) != 0); + rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0); +#endif + + if (!MOUNT_ROOT_RDONLY) + root_mountflags &= ~MS_RDONLY; + init_mm.start_code = (unsigned long) _text; + init_mm.end_code = (unsigned long) _etext; + init_mm.end_data = (unsigned long) _edata; + init_mm.brk = (unsigned long) _end; + + code_resource.start = __pa(_text); + code_resource.end = __pa(_etext)-1; + data_resource.start = __pa(_etext); + data_resource.end = __pa(_edata)-1; + + parse_mem_cmdline(cmdline_p); + + /* + * Find the lowest and highest page frame numbers we have available + */ + first_pfn = PFN_DOWN(memory_start); + last_pfn = PFN_DOWN(memory_end); + pages = last_pfn - first_pfn; + + /* + * Partially used pages are not usable - thus + * we are rounding upwards: + */ + start_pfn = PFN_UP(__pa(_end)); + + /* + * Find a proper area for the bootmem bitmap. After this + * bootstrap step all allocations (until the page allocator + * is intact) must be done via bootmem_alloc(). + */ + bootmap_size = init_bootmem_node(NODE_DATA(0), start_pfn, + first_pfn, + last_pfn); + /* + * Round it up. + */ + bootmap_size = PFN_PHYS(PFN_UP(bootmap_size)); + + /* + * Register fully available RAM pages with the bootmem allocator. + */ + free_bootmem_node(NODE_DATA(0), PFN_PHYS(first_pfn), PFN_PHYS(pages)); + + /* + * Reserve all kernel sections + bootmem bitmap + a guard page. + */ + reserve_bootmem_node(NODE_DATA(0), PFN_PHYS(first_pfn), + (PFN_PHYS(start_pfn) + bootmap_size + PAGE_SIZE) - PFN_PHYS(first_pfn)); + + /* + * Reserve platform dependent sections + */ + platform_reserve(); + +#ifdef CONFIG_BLK_DEV_INITRD + if (LOADER_TYPE && INITRD_START) { + if (INITRD_START + INITRD_SIZE <= (PFN_PHYS(last_pfn))) { + reserve_bootmem_node(NODE_DATA(0), INITRD_START + __MEMORY_START, INITRD_SIZE); + + initrd_start = + (long) INITRD_START ? INITRD_START + PAGE_OFFSET + __MEMORY_START : 0; + + initrd_end = initrd_start + INITRD_SIZE; + } else { + printk("initrd extends beyond end of memory " + "(0x%08lx > 0x%08lx)\ndisabling initrd\n", + (long) INITRD_START + INITRD_SIZE, + PFN_PHYS(last_pfn)); + initrd_start = 0; + } + } +#endif + + /* + * Claim all RAM, ROM, and I/O resources. + */ + + /* Kernel RAM */ + request_resource(&iomem_resource, &code_resource); + request_resource(&iomem_resource, &data_resource); + + /* Other KRAM space */ + for (i = 0; i < STANDARD_KRAM_RESOURCES - 2; i++) + request_resource(&iomem_resource, + &platform_parms.kram_res_p[i]); + + /* XRAM space */ + for (i = 0; i < STANDARD_XRAM_RESOURCES; i++) + request_resource(&iomem_resource, + &platform_parms.xram_res_p[i]); + + /* ROM space */ + for (i = 0; i < STANDARD_ROM_RESOURCES; i++) + request_resource(&iomem_resource, + &platform_parms.rom_res_p[i]); + + /* I/O space */ + for (i = 0; i < STANDARD_IO_RESOURCES; i++) + request_resource(&ioport_resource, + &platform_parms.io_res_p[i]); + + +#ifdef CONFIG_VT +#if defined(CONFIG_VGA_CONSOLE) + conswitchp = &vga_con; +#elif defined(CONFIG_DUMMY_CONSOLE) + conswitchp = &dummy_con; +#endif +#endif + + printk("Hardware FPU: %s\n", fpu_in_use ? "enabled" : "disabled"); + + paging_init(); +} + +void __xchg_called_with_bad_pointer(void) +{ + printk(KERN_EMERG "xchg() called with bad pointer !\n"); +} + +static struct cpu cpu[1]; + +static int __init topology_init(void) +{ + return register_cpu(cpu, 0, NULL); +} + +subsys_initcall(topology_init); + +/* + * Get CPU information + */ +static const char *cpu_name[] = { + [CPU_SH5_101] = "SH5-101", + [CPU_SH5_103] = "SH5-103", + [CPU_SH_NONE] = "Unknown", +}; + +const char *get_cpu_subtype(void) +{ + return cpu_name[boot_cpu_data.type]; +} + +#ifdef CONFIG_PROC_FS +static int show_cpuinfo(struct seq_file *m,void *v) +{ + unsigned int cpu = smp_processor_id(); + + if (!cpu) + seq_printf(m, "machine\t\t: %s\n", get_system_type()); + + seq_printf(m, "processor\t: %d\n", cpu); + seq_printf(m, "cpu family\t: SH-5\n"); + seq_printf(m, "cpu type\t: %s\n", get_cpu_subtype()); + + seq_printf(m, "icache size\t: %dK-bytes\n", + (boot_cpu_data.icache.ways * + boot_cpu_data.icache.sets * + boot_cpu_data.icache.linesz) >> 10); + seq_printf(m, "dcache size\t: %dK-bytes\n", + (boot_cpu_data.dcache.ways * + boot_cpu_data.dcache.sets * + boot_cpu_data.dcache.linesz) >> 10); + seq_printf(m, "itlb entries\t: %d\n", boot_cpu_data.itlb.entries); + seq_printf(m, "dtlb entries\t: %d\n", boot_cpu_data.dtlb.entries); + +#define PRINT_CLOCK(name, value) \ + seq_printf(m, name " clock\t: %d.%02dMHz\n", \ + ((value) / 1000000), ((value) % 1000000)/10000) + + PRINT_CLOCK("cpu", boot_cpu_data.cpu_clock); + PRINT_CLOCK("bus", boot_cpu_data.bus_clock); + PRINT_CLOCK("module", boot_cpu_data.module_clock); + + seq_printf(m, "bogomips\t: %lu.%02lu\n\n", + (loops_per_jiffy*HZ+2500)/500000, + ((loops_per_jiffy*HZ+2500)/5000) % 100); + + return 0; +} + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return (void*)(*pos == 0); +} +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + return NULL; +} +static void c_stop(struct seq_file *m, void *v) +{ +} +struct seq_operations cpuinfo_op = { + .start = c_start, + .next = c_next, + .stop = c_stop, + .show = show_cpuinfo, +}; +#endif /* CONFIG_PROC_FS */ diff --git a/arch/sh64/kernel/sh_ksyms.c b/arch/sh64/kernel/sh_ksyms.c new file mode 100644 index 00000000000..0b5497d70bd --- /dev/null +++ b/arch/sh64/kernel/sh_ksyms.c @@ -0,0 +1,89 @@ +/* + * 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. + * + * arch/sh64/kernel/sh_ksyms.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * + */ + +#include <linux/config.h> +#include <linux/rwsem.h> +#include <linux/module.h> +#include <linux/smp.h> +#include <linux/user.h> +#include <linux/elfcore.h> +#include <linux/sched.h> +#include <linux/in6.h> +#include <linux/interrupt.h> +#include <linux/smp_lock.h> +#include <linux/tty.h> + +#include <asm/semaphore.h> +#include <asm/processor.h> +#include <asm/uaccess.h> +#include <asm/checksum.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/irq.h> + +extern void dump_thread(struct pt_regs *, struct user *); +extern int dump_fpu(struct pt_regs *, elf_fpregset_t *); + +#if 0 +/* Not yet - there's no declaration of drive_info anywhere. */ +#if defined(CONFIG_BLK_DEV_IDE) || defined(CONFIG_BLK_DEV_HD) || defined(CONFIG_BLK_DEV_IDE_MODULE) || defined(CONFIG_BLK_DEV_HD_MODULE) +extern struct drive_info_struct drive_info; +EXPORT_SYMBOL(drive_info); +#endif +#endif + +/* platform dependent support */ +EXPORT_SYMBOL(dump_thread); +EXPORT_SYMBOL(dump_fpu); +EXPORT_SYMBOL(iounmap); +EXPORT_SYMBOL(enable_irq); +EXPORT_SYMBOL(disable_irq); +EXPORT_SYMBOL(kernel_thread); + +/* Networking helper routines. */ +EXPORT_SYMBOL(csum_partial_copy); + +EXPORT_SYMBOL(strpbrk); +EXPORT_SYMBOL(strstr); + +#ifdef CONFIG_VT +EXPORT_SYMBOL(screen_info); +#endif + +EXPORT_SYMBOL(__down); +EXPORT_SYMBOL(__down_trylock); +EXPORT_SYMBOL(__up); +EXPORT_SYMBOL(__put_user_asm_l); +EXPORT_SYMBOL(__get_user_asm_l); +EXPORT_SYMBOL(memcmp); +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memset); +EXPORT_SYMBOL(memscan); +EXPORT_SYMBOL(strchr); +EXPORT_SYMBOL(strlen); + +EXPORT_SYMBOL(flush_dcache_page); + +/* For ext3 */ +EXPORT_SYMBOL(sh64_page_clear); + +/* Ugh. These come in from libgcc.a at link time. */ + +extern void __sdivsi3(void); +extern void __muldi3(void); +extern void __udivsi3(void); +extern char __div_table; +EXPORT_SYMBOL(__sdivsi3); +EXPORT_SYMBOL(__muldi3); +EXPORT_SYMBOL(__udivsi3); +EXPORT_SYMBOL(__div_table); + + diff --git a/arch/sh64/kernel/signal.c b/arch/sh64/kernel/signal.c new file mode 100644 index 00000000000..45ad1026dde --- /dev/null +++ b/arch/sh64/kernel/signal.c @@ -0,0 +1,727 @@ +/* + * 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. + * + * arch/sh64/kernel/signal.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003 Paul Mundt + * Copyright (C) 2004 Richard Curnow + * + * Started from sh version. + * + */ +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/personality.h> +#include <linux/suspend.h> +#include <linux/ptrace.h> +#include <linux/unistd.h> +#include <linux/stddef.h> +#include <linux/personality.h> +#include <asm/ucontext.h> +#include <asm/uaccess.h> +#include <asm/pgtable.h> + + +#define REG_RET 9 +#define REG_ARG1 2 +#define REG_ARG2 3 +#define REG_ARG3 4 +#define REG_SP 15 +#define REG_PR 18 +#define REF_REG_RET regs->regs[REG_RET] +#define REF_REG_SP regs->regs[REG_SP] +#define DEREF_REG_PR regs->regs[REG_PR] + +#define DEBUG_SIG 0 + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +asmlinkage int do_signal(struct pt_regs *regs, sigset_t *oldset); + +/* + * Atomically swap in the new signal mask, and wait for a signal. + */ + +asmlinkage int +sys_sigsuspend(old_sigset_t mask, + unsigned long r3, unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs * regs) +{ + sigset_t saveset; + + mask &= _BLOCKABLE; + spin_lock_irq(¤t->sighand->siglock); + saveset = current->blocked; + siginitset(¤t->blocked, mask); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + REF_REG_RET = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + regs->pc += 4; /* because sys_sigreturn decrements the pc */ + if (do_signal(regs, &saveset)) { + /* pc now points at signal handler. Need to decrement + it because entry.S will increment it. */ + regs->pc -= 4; + return -EINTR; + } + } +} + +asmlinkage int +sys_rt_sigsuspend(sigset_t *unewset, size_t sigsetsize, + unsigned long r4, unsigned long r5, unsigned long r6, + unsigned long r7, + struct pt_regs * regs) +{ + sigset_t saveset, newset; + + /* XXX: Don't preclude handling different sized sigset_t's. */ + if (sigsetsize != sizeof(sigset_t)) + return -EINVAL; + + if (copy_from_user(&newset, unewset, sizeof(newset))) + return -EFAULT; + sigdelsetmask(&newset, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + saveset = current->blocked; + current->blocked = newset; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + REF_REG_RET = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + regs->pc += 4; /* because sys_sigreturn decrements the pc */ + if (do_signal(regs, &saveset)) { + /* pc now points at signal handler. Need to decrement + it because entry.S will increment it. */ + regs->pc -= 4; + return -EINTR; + } + } +} + +asmlinkage int +sys_sigaction(int sig, const struct old_sigaction __user *act, + struct old_sigaction __user *oact) +{ + struct k_sigaction new_ka, old_ka; + int ret; + + if (act) { + old_sigset_t mask; + if (!access_ok(VERIFY_READ, act, sizeof(*act)) || + __get_user(new_ka.sa.sa_handler, &act->sa_handler) || + __get_user(new_ka.sa.sa_restorer, &act->sa_restorer)) + return -EFAULT; + __get_user(new_ka.sa.sa_flags, &act->sa_flags); + __get_user(mask, &act->sa_mask); + siginitset(&new_ka.sa.sa_mask, mask); + } + + ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL); + + if (!ret && oact) { + if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || + __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer)) + return -EFAULT; + __put_user(old_ka.sa.sa_flags, &oact->sa_flags); + __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return ret; +} + +asmlinkage int +sys_sigaltstack(const stack_t __user *uss, stack_t __user *uoss, + unsigned long r4, unsigned long r5, unsigned long r6, + unsigned long r7, + struct pt_regs * regs) +{ + return do_sigaltstack(uss, uoss, REF_REG_SP); +} + + +/* + * Do a signal return; undo the signal stack. + */ + +struct sigframe +{ + struct sigcontext sc; + unsigned long extramask[_NSIG_WORDS-1]; + long long retcode[2]; +}; + +struct rt_sigframe +{ + struct siginfo __user *pinfo; + void *puc; + struct siginfo info; + struct ucontext uc; + long long retcode[2]; +}; + +#ifdef CONFIG_SH_FPU +static inline int +restore_sigcontext_fpu(struct pt_regs *regs, struct sigcontext __user *sc) +{ + int err = 0; + int fpvalid; + + err |= __get_user (fpvalid, &sc->sc_fpvalid); + conditional_used_math(fpvalid); + if (! fpvalid) + return err; + + if (current == last_task_used_math) { + last_task_used_math = NULL; + regs->sr |= SR_FD; + } + + err |= __copy_from_user(¤t->thread.fpu.hard, &sc->sc_fpregs[0], + (sizeof(long long) * 32) + (sizeof(int) * 1)); + + return err; +} + +static inline int +setup_sigcontext_fpu(struct pt_regs *regs, struct sigcontext __user *sc) +{ + int err = 0; + int fpvalid; + + fpvalid = !!used_math(); + err |= __put_user(fpvalid, &sc->sc_fpvalid); + if (! fpvalid) + return err; + + if (current == last_task_used_math) { + grab_fpu(); + fpsave(¤t->thread.fpu.hard); + release_fpu(); + last_task_used_math = NULL; + regs->sr |= SR_FD; + } + + err |= __copy_to_user(&sc->sc_fpregs[0], ¤t->thread.fpu.hard, + (sizeof(long long) * 32) + (sizeof(int) * 1)); + clear_used_math(); + + return err; +} +#else +static inline int +restore_sigcontext_fpu(struct pt_regs *regs, struct sigcontext __user *sc) +{} +static inline int +setup_sigcontext_fpu(struct pt_regs *regs, struct sigcontext __user *sc) +{} +#endif + +static int +restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc, long long *r2_p) +{ + unsigned int err = 0; + unsigned long long current_sr, new_sr; +#define SR_MASK 0xffff8cfd + +#define COPY(x) err |= __get_user(regs->x, &sc->sc_##x) + + COPY(regs[0]); COPY(regs[1]); COPY(regs[2]); COPY(regs[3]); + COPY(regs[4]); COPY(regs[5]); COPY(regs[6]); COPY(regs[7]); + COPY(regs[8]); COPY(regs[9]); COPY(regs[10]); COPY(regs[11]); + COPY(regs[12]); COPY(regs[13]); COPY(regs[14]); COPY(regs[15]); + COPY(regs[16]); COPY(regs[17]); COPY(regs[18]); COPY(regs[19]); + COPY(regs[20]); COPY(regs[21]); COPY(regs[22]); COPY(regs[23]); + COPY(regs[24]); COPY(regs[25]); COPY(regs[26]); COPY(regs[27]); + COPY(regs[28]); COPY(regs[29]); COPY(regs[30]); COPY(regs[31]); + COPY(regs[32]); COPY(regs[33]); COPY(regs[34]); COPY(regs[35]); + COPY(regs[36]); COPY(regs[37]); COPY(regs[38]); COPY(regs[39]); + COPY(regs[40]); COPY(regs[41]); COPY(regs[42]); COPY(regs[43]); + COPY(regs[44]); COPY(regs[45]); COPY(regs[46]); COPY(regs[47]); + COPY(regs[48]); COPY(regs[49]); COPY(regs[50]); COPY(regs[51]); + COPY(regs[52]); COPY(regs[53]); COPY(regs[54]); COPY(regs[55]); + COPY(regs[56]); COPY(regs[57]); COPY(regs[58]); COPY(regs[59]); + COPY(regs[60]); COPY(regs[61]); COPY(regs[62]); + COPY(tregs[0]); COPY(tregs[1]); COPY(tregs[2]); COPY(tregs[3]); + COPY(tregs[4]); COPY(tregs[5]); COPY(tregs[6]); COPY(tregs[7]); + + /* Prevent the signal handler manipulating SR in a way that can + crash the kernel. i.e. only allow S, Q, M, PR, SZ, FR to be + modified */ + current_sr = regs->sr; + err |= __get_user(new_sr, &sc->sc_sr); + regs->sr &= SR_MASK; + regs->sr |= (new_sr & ~SR_MASK); + + COPY(pc); + +#undef COPY + + /* Must do this last in case it sets regs->sr.fd (i.e. after rest of sr + * has been restored above.) */ + err |= restore_sigcontext_fpu(regs, sc); + + regs->syscall_nr = -1; /* disable syscall checks */ + err |= __get_user(*r2_p, &sc->sc_regs[REG_RET]); + return err; +} + +asmlinkage int sys_sigreturn(unsigned long r2, unsigned long r3, + unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs * regs) +{ + struct sigframe __user *frame = (struct sigframe __user *) (long) REF_REG_SP; + sigset_t set; + long long ret; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (__get_user(set.sig[0], &frame->sc.oldmask) + || (_NSIG_WORDS > 1 + && __copy_from_user(&set.sig[1], &frame->extramask, + sizeof(frame->extramask)))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->sc, &ret)) + goto badframe; + regs->pc -= 4; + + return (int) ret; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +asmlinkage int sys_rt_sigreturn(unsigned long r2, unsigned long r3, + unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs * regs) +{ + struct rt_sigframe __user *frame = (struct rt_sigframe __user *) (long) REF_REG_SP; + sigset_t set; + stack_t __user st; + long long ret; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + current->blocked = set; + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext, &ret)) + goto badframe; + regs->pc -= 4; + + if (__copy_from_user(&st, &frame->uc.uc_stack, sizeof(st))) + goto badframe; + /* It is more difficult to avoid calling this function than to + call it and ignore errors. */ + do_sigaltstack(&st, NULL, REF_REG_SP); + + return (int) ret; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* + * Set up a signal frame. + */ + +static int +setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs, + unsigned long mask) +{ + int err = 0; + + /* Do this first, otherwise is this sets sr->fd, that value isn't preserved. */ + err |= setup_sigcontext_fpu(regs, sc); + +#define COPY(x) err |= __put_user(regs->x, &sc->sc_##x) + + COPY(regs[0]); COPY(regs[1]); COPY(regs[2]); COPY(regs[3]); + COPY(regs[4]); COPY(regs[5]); COPY(regs[6]); COPY(regs[7]); + COPY(regs[8]); COPY(regs[9]); COPY(regs[10]); COPY(regs[11]); + COPY(regs[12]); COPY(regs[13]); COPY(regs[14]); COPY(regs[15]); + COPY(regs[16]); COPY(regs[17]); COPY(regs[18]); COPY(regs[19]); + COPY(regs[20]); COPY(regs[21]); COPY(regs[22]); COPY(regs[23]); + COPY(regs[24]); COPY(regs[25]); COPY(regs[26]); COPY(regs[27]); + COPY(regs[28]); COPY(regs[29]); COPY(regs[30]); COPY(regs[31]); + COPY(regs[32]); COPY(regs[33]); COPY(regs[34]); COPY(regs[35]); + COPY(regs[36]); COPY(regs[37]); COPY(regs[38]); COPY(regs[39]); + COPY(regs[40]); COPY(regs[41]); COPY(regs[42]); COPY(regs[43]); + COPY(regs[44]); COPY(regs[45]); COPY(regs[46]); COPY(regs[47]); + COPY(regs[48]); COPY(regs[49]); COPY(regs[50]); COPY(regs[51]); + COPY(regs[52]); COPY(regs[53]); COPY(regs[54]); COPY(regs[55]); + COPY(regs[56]); COPY(regs[57]); COPY(regs[58]); COPY(regs[59]); + COPY(regs[60]); COPY(regs[61]); COPY(regs[62]); + COPY(tregs[0]); COPY(tregs[1]); COPY(tregs[2]); COPY(tregs[3]); + COPY(tregs[4]); COPY(tregs[5]); COPY(tregs[6]); COPY(tregs[7]); + COPY(sr); COPY(pc); + +#undef COPY + + err |= __put_user(mask, &sc->oldmask); + + return err; +} + +/* + * Determine which stack to use.. + */ +static inline void __user * +get_sigframe(struct k_sigaction *ka, unsigned long sp, size_t frame_size) +{ + if ((ka->sa.sa_flags & SA_ONSTACK) != 0 && ! on_sig_stack(sp)) + sp = current->sas_ss_sp + current->sas_ss_size; + + return (void __user *)((sp - frame_size) & -8ul); +} + +void sa_default_restorer(void); /* See comments below */ +void sa_default_rt_restorer(void); /* See comments below */ + +static void setup_frame(int sig, struct k_sigaction *ka, + sigset_t *set, struct pt_regs *regs) +{ + struct sigframe __user *frame; + int err = 0; + int signal; + + frame = get_sigframe(ka, regs->regs[REG_SP], sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + signal = current_thread_info()->exec_domain + && current_thread_info()->exec_domain->signal_invmap + && sig < 32 + ? current_thread_info()->exec_domain->signal_invmap[sig] + : sig; + + err |= setup_sigcontext(&frame->sc, regs, set->sig[0]); + + /* Give up earlier as i386, in case */ + if (err) + goto give_sigsegv; + + if (_NSIG_WORDS > 1) { + err |= __copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask)); } + + /* Give up earlier as i386, in case */ + if (err) + goto give_sigsegv; + + /* Set up to return from userspace. If provided, use a stub + already in userspace. */ + if (ka->sa.sa_flags & SA_RESTORER) { + DEREF_REG_PR = (unsigned long) ka->sa.sa_restorer | 0x1; + + /* + * On SH5 all edited pointers are subject to NEFF + */ + DEREF_REG_PR = (DEREF_REG_PR & NEFF_SIGN) ? + (DEREF_REG_PR | NEFF_MASK) : DEREF_REG_PR; + } else { + /* + * Different approach on SH5. + * . Endianness independent asm code gets placed in entry.S . + * This is limited to four ASM instructions corresponding + * to two long longs in size. + * . err checking is done on the else branch only + * . flush_icache_range() is called upon __put_user() only + * . all edited pointers are subject to NEFF + * . being code, linker turns ShMedia bit on, always + * dereference index -1. + */ + DEREF_REG_PR = (unsigned long) frame->retcode | 0x01; + DEREF_REG_PR = (DEREF_REG_PR & NEFF_SIGN) ? + (DEREF_REG_PR | NEFF_MASK) : DEREF_REG_PR; + + if (__copy_to_user(frame->retcode, + (unsigned long long)sa_default_restorer & (~1), 16) != 0) + goto give_sigsegv; + + /* Cohere the trampoline with the I-cache. */ + flush_cache_sigtramp(DEREF_REG_PR-1, DEREF_REG_PR-1+16); + } + + /* + * Set up registers for signal handler. + * All edited pointers are subject to NEFF. + */ + regs->regs[REG_SP] = (unsigned long) frame; + regs->regs[REG_SP] = (regs->regs[REG_SP] & NEFF_SIGN) ? + (regs->regs[REG_SP] | NEFF_MASK) : regs->regs[REG_SP]; + regs->regs[REG_ARG1] = signal; /* Arg for signal handler */ + + /* FIXME: + The glibc profiling support for SH-5 needs to be passed a sigcontext + so it can retrieve the PC. At some point during 2003 the glibc + support was changed to receive the sigcontext through the 2nd + argument, but there are still versions of libc.so in use that use + the 3rd argument. Until libc.so is stabilised, pass the sigcontext + through both 2nd and 3rd arguments. + */ + + regs->regs[REG_ARG2] = (unsigned long long)(unsigned long)(signed long)&frame->sc; + regs->regs[REG_ARG3] = (unsigned long long)(unsigned long)(signed long)&frame->sc; + + regs->pc = (unsigned long) ka->sa.sa_handler; + regs->pc = (regs->pc & NEFF_SIGN) ? (regs->pc | NEFF_MASK) : regs->pc; + + set_fs(USER_DS); + +#if DEBUG_SIG + /* Broken %016Lx */ + printk("SIG deliver (#%d,%s:%d): sp=%p pc=%08Lx%08Lx link=%08Lx%08Lx\n", + signal, + current->comm, current->pid, frame, + regs->pc >> 32, regs->pc & 0xffffffff, + DEREF_REG_PR >> 32, DEREF_REG_PR & 0xffffffff); +#endif + + return; + +give_sigsegv: + force_sigsegv(sig, current); +} + +static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info, + sigset_t *set, struct pt_regs *regs) +{ + struct rt_sigframe __user *frame; + int err = 0; + int signal; + + frame = get_sigframe(ka, regs->regs[REG_SP], sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + signal = current_thread_info()->exec_domain + && current_thread_info()->exec_domain->signal_invmap + && sig < 32 + ? current_thread_info()->exec_domain->signal_invmap[sig] + : sig; + + err |= __put_user(&frame->info, &frame->pinfo); + err |= __put_user(&frame->uc, &frame->puc); + err |= copy_siginfo_to_user(&frame->info, info); + + /* Give up earlier as i386, in case */ + if (err) + goto give_sigsegv; + + /* Create the ucontext. */ + err |= __put_user(0, &frame->uc.uc_flags); + err |= __put_user(0, &frame->uc.uc_link); + err |= __put_user((void *)current->sas_ss_sp, + &frame->uc.uc_stack.ss_sp); + err |= __put_user(sas_ss_flags(regs->regs[REG_SP]), + &frame->uc.uc_stack.ss_flags); + err |= __put_user(current->sas_ss_size, &frame->uc.uc_stack.ss_size); + err |= setup_sigcontext(&frame->uc.uc_mcontext, + regs, set->sig[0]); + err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + /* Give up earlier as i386, in case */ + if (err) + goto give_sigsegv; + + /* Set up to return from userspace. If provided, use a stub + already in userspace. */ + if (ka->sa.sa_flags & SA_RESTORER) { + DEREF_REG_PR = (unsigned long) ka->sa.sa_restorer | 0x1; + + /* + * On SH5 all edited pointers are subject to NEFF + */ + DEREF_REG_PR = (DEREF_REG_PR & NEFF_SIGN) ? + (DEREF_REG_PR | NEFF_MASK) : DEREF_REG_PR; + } else { + /* + * Different approach on SH5. + * . Endianness independent asm code gets placed in entry.S . + * This is limited to four ASM instructions corresponding + * to two long longs in size. + * . err checking is done on the else branch only + * . flush_icache_range() is called upon __put_user() only + * . all edited pointers are subject to NEFF + * . being code, linker turns ShMedia bit on, always + * dereference index -1. + */ + + DEREF_REG_PR = (unsigned long) frame->retcode | 0x01; + DEREF_REG_PR = (DEREF_REG_PR & NEFF_SIGN) ? + (DEREF_REG_PR | NEFF_MASK) : DEREF_REG_PR; + + if (__copy_to_user(frame->retcode, + (unsigned long long)sa_default_rt_restorer & (~1), 16) != 0) + goto give_sigsegv; + + flush_icache_range(DEREF_REG_PR-1, DEREF_REG_PR-1+15); + } + + /* + * Set up registers for signal handler. + * All edited pointers are subject to NEFF. + */ + regs->regs[REG_SP] = (unsigned long) frame; + regs->regs[REG_SP] = (regs->regs[REG_SP] & NEFF_SIGN) ? + (regs->regs[REG_SP] | NEFF_MASK) : regs->regs[REG_SP]; + regs->regs[REG_ARG1] = signal; /* Arg for signal handler */ + regs->regs[REG_ARG2] = (unsigned long long)(unsigned long)(signed long)&frame->info; + regs->regs[REG_ARG3] = (unsigned long long)(unsigned long)(signed long)&frame->uc.uc_mcontext; + regs->pc = (unsigned long) ka->sa.sa_handler; + regs->pc = (regs->pc & NEFF_SIGN) ? (regs->pc | NEFF_MASK) : regs->pc; + + set_fs(USER_DS); + +#if DEBUG_SIG + /* Broken %016Lx */ + printk("SIG deliver (#%d,%s:%d): sp=%p pc=%08Lx%08Lx link=%08Lx%08Lx\n", + signal, + current->comm, current->pid, frame, + regs->pc >> 32, regs->pc & 0xffffffff, + DEREF_REG_PR >> 32, DEREF_REG_PR & 0xffffffff); +#endif + + return; + +give_sigsegv: + force_sigsegv(sig, current); +} + +/* + * OK, we're invoking a handler + */ + +static void +handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka, + sigset_t *oldset, struct pt_regs * regs) +{ + /* Are we from a system call? */ + if (regs->syscall_nr >= 0) { + /* If so, check system call restarting.. */ + switch (regs->regs[REG_RET]) { + case -ERESTARTNOHAND: + regs->regs[REG_RET] = -EINTR; + break; + + case -ERESTARTSYS: + if (!(ka->sa.sa_flags & SA_RESTART)) { + regs->regs[REG_RET] = -EINTR; + break; + } + /* fallthrough */ + case -ERESTARTNOINTR: + /* Decode syscall # */ + regs->regs[REG_RET] = regs->syscall_nr; + regs->pc -= 4; + } + } + + /* Set up the stack frame */ + if (ka->sa.sa_flags & SA_SIGINFO) + setup_rt_frame(sig, ka, info, oldset, regs); + else + setup_frame(sig, ka, oldset, regs); + + if (!(ka->sa.sa_flags & SA_NODEFER)) { + spin_lock_irq(¤t->sighand->siglock); + sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask); + sigaddset(¤t->blocked,sig); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } +} + +/* + * Note that 'init' is a special process: it doesn't get signals it doesn't + * want to handle. Thus you cannot kill init even with a SIGKILL even by + * mistake. + * + * Note that we go through the signals twice: once to check the signals that + * the kernel can handle, and then we build all the user-level signal handling + * stack-frames in one go after that. + */ +int do_signal(struct pt_regs *regs, sigset_t *oldset) +{ + siginfo_t info; + int signr; + struct k_sigaction ka; + + /* + * We want the common case to go fast, which + * is why we may in certain cases get here from + * kernel mode. Just return without doing anything + * if so. + */ + if (!user_mode(regs)) + return 1; + + if (try_to_freeze(0)) + goto no_signal; + + if (!oldset) + oldset = ¤t->blocked; + + signr = get_signal_to_deliver(&info, &ka, regs, 0); + + if (signr > 0) { + /* Whee! Actually deliver the signal. */ + handle_signal(signr, &info, &ka, oldset, regs); + return 1; + } + +no_signal: + /* Did we come from a system call? */ + if (regs->syscall_nr >= 0) { + /* Restart the system call - no handlers present */ + if (regs->regs[REG_RET] == -ERESTARTNOHAND || + regs->regs[REG_RET] == -ERESTARTSYS || + regs->regs[REG_RET] == -ERESTARTNOINTR) { + /* Decode Syscall # */ + regs->regs[REG_RET] = regs->syscall_nr; + regs->pc -= 4; + } + } + return 0; +} diff --git a/arch/sh64/kernel/switchto.S b/arch/sh64/kernel/switchto.S new file mode 100644 index 00000000000..45b2d90eed7 --- /dev/null +++ b/arch/sh64/kernel/switchto.S @@ -0,0 +1,198 @@ +/* + * arch/sh64/kernel/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 + diff --git a/arch/sh64/kernel/sys_sh64.c b/arch/sh64/kernel/sys_sh64.c new file mode 100644 index 00000000000..4546845b9ca --- /dev/null +++ b/arch/sh64/kernel/sys_sh64.c @@ -0,0 +1,300 @@ +/* + * 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. + * + * arch/sh64/kernel/sys_sh64.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * + * This file contains various random system calls that + * have a non-standard calling sequence on the Linux/SH5 + * platform. + * + * Mostly taken from i386 version. + * + */ + +#include <linux/errno.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/sem.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/stat.h> +#include <linux/mman.h> +#include <linux/file.h> +#include <linux/utsname.h> +#include <linux/syscalls.h> +#include <asm/uaccess.h> +#include <asm/ipc.h> +#include <asm/ptrace.h> + +#define REG_3 3 + +/* + * sys_pipe() is the normal C calling standard for creating + * a pipe. It's not the way Unix traditionally does this, though. + */ +#ifdef NEW_PIPE_IMPLEMENTATION +asmlinkage int sys_pipe(unsigned long * fildes, + unsigned long dummy_r3, + unsigned long dummy_r4, + unsigned long dummy_r5, + unsigned long dummy_r6, + unsigned long dummy_r7, + struct pt_regs * regs) /* r8 = pt_regs forced by entry.S */ +{ + int fd[2]; + int ret; + + ret = do_pipe(fd); + if (ret == 0) + /* + *********************************************************************** + * To avoid the copy_to_user we prefer to break the ABIs convention, * + * packing the valid pair of file IDs into a single register (r3); * + * while r2 is the return code as defined by the sh5-ABIs. * + * BE CAREFUL: pipe stub, into glibc, must be aware of this solution * + *********************************************************************** + +#ifdef __LITTLE_ENDIAN__ + regs->regs[REG_3] = (((unsigned long long) fd[1]) << 32) | ((unsigned long long) fd[0]); +#else + regs->regs[REG_3] = (((unsigned long long) fd[0]) << 32) | ((unsigned long long) fd[1]); +#endif + + */ + /* although not very clever this is endianess independent */ + regs->regs[REG_3] = (unsigned long long) *((unsigned long long *) fd); + + return ret; +} + +#else +asmlinkage int sys_pipe(unsigned long * fildes) +{ + int fd[2]; + int error; + + error = do_pipe(fd); + if (!error) { + if (copy_to_user(fildes, fd, 2*sizeof(int))) + error = -EFAULT; + } + return error; +} + +#endif + +/* + * To avoid cache alias, we map the shard page with same color. + */ +#define COLOUR_ALIGN(addr) (((addr)+SHMLBA-1)&~(SHMLBA-1)) + +unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr, + unsigned long len, unsigned long pgoff, unsigned long flags) +{ + struct vm_area_struct *vma; + + if (flags & MAP_FIXED) { + /* We do not accept a shared mapping if it would violate + * cache aliasing constraints. + */ + if ((flags & MAP_SHARED) && (addr & (SHMLBA - 1))) + return -EINVAL; + return addr; + } + + if (len > TASK_SIZE) + return -ENOMEM; + if (!addr) + addr = TASK_UNMAPPED_BASE; + + if (flags & MAP_PRIVATE) + addr = PAGE_ALIGN(addr); + else + addr = COLOUR_ALIGN(addr); + + for (vma = find_vma(current->mm, addr); ; vma = vma->vm_next) { + /* At this point: (!vma || addr < vma->vm_end). */ + if (TASK_SIZE - len < addr) + return -ENOMEM; + if (!vma || addr + len <= vma->vm_start) + return addr; + addr = vma->vm_end; + if (!(flags & MAP_PRIVATE)) + addr = COLOUR_ALIGN(addr); + } +} + +/* common code for old and new mmaps */ +static inline long do_mmap2( + unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long pgoff) +{ + int error = -EBADF; + struct file * file = NULL; + + flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); + if (!(flags & MAP_ANONYMOUS)) { + file = fget(fd); + if (!file) + goto out; + } + + down_write(¤t->mm->mmap_sem); + error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); + up_write(¤t->mm->mmap_sem); + + if (file) + fput(file); +out: + return error; +} + +asmlinkage long sys_mmap2(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + unsigned long fd, unsigned long pgoff) +{ + return do_mmap2(addr, len, prot, flags, fd, pgoff); +} + +asmlinkage int old_mmap(unsigned long addr, unsigned long len, + unsigned long prot, unsigned long flags, + int fd, unsigned long off) +{ + if (off & ~PAGE_MASK) + return -EINVAL; + return do_mmap2(addr, len, prot, flags, fd, off>>PAGE_SHIFT); +} + +/* + * sys_ipc() is the de-multiplexer for the SysV IPC calls.. + * + * This is really horribly ugly. + */ +asmlinkage int sys_ipc(uint call, int first, int second, + int third, void __user *ptr, long fifth) +{ + int version, ret; + + version = call >> 16; /* hack for backward compatibility */ + call &= 0xffff; + + if (call <= SEMCTL) + switch (call) { + case SEMOP: + return sys_semtimedop(first, (struct sembuf __user *)ptr, + second, NULL); + case SEMTIMEDOP: + return sys_semtimedop(first, (struct sembuf __user *)ptr, + second, + (const struct timespec __user *)fifth); + case SEMGET: + return sys_semget (first, second, third); + case SEMCTL: { + union semun fourth; + if (!ptr) + return -EINVAL; + if (get_user(fourth.__pad, (void * __user *) ptr)) + return -EFAULT; + return sys_semctl (first, second, third, fourth); + } + default: + return -EINVAL; + } + + if (call <= MSGCTL) + switch (call) { + case MSGSND: + return sys_msgsnd (first, (struct msgbuf __user *) ptr, + second, third); + case MSGRCV: + switch (version) { + case 0: { + struct ipc_kludge tmp; + if (!ptr) + return -EINVAL; + + if (copy_from_user(&tmp, + (struct ipc_kludge __user *) ptr, + sizeof (tmp))) + return -EFAULT; + return sys_msgrcv (first, tmp.msgp, second, + tmp.msgtyp, third); + } + default: + return sys_msgrcv (first, + (struct msgbuf __user *) ptr, + second, fifth, third); + } + case MSGGET: + return sys_msgget ((key_t) first, second); + case MSGCTL: + return sys_msgctl (first, second, + (struct msqid_ds __user *) ptr); + default: + return -EINVAL; + } + if (call <= SHMCTL) + switch (call) { + case SHMAT: + switch (version) { + default: { + ulong raddr; + ret = do_shmat (first, (char __user *) ptr, + second, &raddr); + if (ret) + return ret; + return put_user (raddr, (ulong __user *) third); + } + case 1: /* iBCS2 emulator entry point */ + if (!segment_eq(get_fs(), get_ds())) + return -EINVAL; + return do_shmat (first, (char __user *) ptr, + second, (ulong *) third); + } + case SHMDT: + return sys_shmdt ((char __user *)ptr); + case SHMGET: + return sys_shmget (first, second, third); + case SHMCTL: + return sys_shmctl (first, second, + (struct shmid_ds __user *) ptr); + default: + return -EINVAL; + } + + return -EINVAL; +} + +asmlinkage int sys_uname(struct old_utsname * name) +{ + int err; + if (!name) + return -EFAULT; + down_read(&uts_sem); + err=copy_to_user(name, &system_utsname, sizeof (*name)); + up_read(&uts_sem); + return err?-EFAULT:0; +} + +/* Copy from mips version */ +asmlinkage long sys_shmatcall(int shmid, char __user *shmaddr, + int shmflg) +{ + unsigned long raddr; + int err; + + err = do_shmat(shmid, shmaddr, shmflg, &raddr); + if (err) + return err; + + err = raddr; + return err; +} diff --git a/arch/sh64/kernel/syscalls.S b/arch/sh64/kernel/syscalls.S new file mode 100644 index 00000000000..8ed417df3dc --- /dev/null +++ b/arch/sh64/kernel/syscalls.S @@ -0,0 +1,345 @@ +/* + * arch/sh64/kernel/syscalls.S + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2004 Paul Mundt + * Copyright (C) 2003, 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/sys.h> + + .section .data, "aw" + .balign 32 + +/* + * System calls jump table + */ + .globl sys_call_table +sys_call_table: + .long sys_ni_syscall /* 0 - old "setup()" system call */ + .long sys_exit + .long sys_fork + .long sys_read + .long sys_write + .long sys_open /* 5 */ + .long sys_close + .long sys_waitpid + .long sys_creat + .long sys_link + .long sys_unlink /* 10 */ + .long sys_execve + .long sys_chdir + .long sys_time + .long sys_mknod + .long sys_chmod /* 15 */ + .long sys_lchown16 + .long sys_ni_syscall /* old break syscall holder */ + .long sys_stat + .long sys_lseek + .long sys_getpid /* 20 */ + .long sys_mount + .long sys_oldumount + .long sys_setuid16 + .long sys_getuid16 + .long sys_stime /* 25 */ + .long sys_ptrace + .long sys_alarm + .long sys_fstat + .long sys_pause + .long sys_utime /* 30 */ + .long sys_ni_syscall /* old stty syscall holder */ + .long sys_ni_syscall /* old gtty syscall holder */ + .long sys_access + .long sys_nice + .long sys_ni_syscall /* 35 */ /* old ftime syscall holder */ + .long sys_sync + .long sys_kill + .long sys_rename + .long sys_mkdir + .long sys_rmdir /* 40 */ + .long sys_dup + .long sys_pipe + .long sys_times + .long sys_ni_syscall /* old prof syscall holder */ + .long sys_brk /* 45 */ + .long sys_setgid16 + .long sys_getgid16 + .long sys_signal + .long sys_geteuid16 + .long sys_getegid16 /* 50 */ + .long sys_acct + .long sys_umount /* recycled never used phys( */ + .long sys_ni_syscall /* old lock syscall holder */ + .long sys_ioctl + .long sys_fcntl /* 55 */ + .long sys_ni_syscall /* old mpx syscall holder */ + .long sys_setpgid + .long sys_ni_syscall /* old ulimit syscall holder */ + .long sys_ni_syscall /* sys_olduname */ + .long sys_umask /* 60 */ + .long sys_chroot + .long sys_ustat + .long sys_dup2 + .long sys_getppid + .long sys_getpgrp /* 65 */ + .long sys_setsid + .long sys_sigaction + .long sys_sgetmask + .long sys_ssetmask + .long sys_setreuid16 /* 70 */ + .long sys_setregid16 + .long sys_sigsuspend + .long sys_sigpending + .long sys_sethostname + .long sys_setrlimit /* 75 */ + .long sys_old_getrlimit + .long sys_getrusage + .long sys_gettimeofday + .long sys_settimeofday + .long sys_getgroups16 /* 80 */ + .long sys_setgroups16 + .long sys_ni_syscall /* sys_oldselect */ + .long sys_symlink + .long sys_lstat + .long sys_readlink /* 85 */ + .long sys_uselib + .long sys_swapon + .long sys_reboot + .long old_readdir + .long old_mmap /* 90 */ + .long sys_munmap + .long sys_truncate + .long sys_ftruncate + .long sys_fchmod + .long sys_fchown16 /* 95 */ + .long sys_getpriority + .long sys_setpriority + .long sys_ni_syscall /* old profil syscall holder */ + .long sys_statfs + .long sys_fstatfs /* 100 */ + .long sys_ni_syscall /* ioperm */ + .long sys_socketcall /* Obsolete implementation of socket syscall */ + .long sys_syslog + .long sys_setitimer + .long sys_getitimer /* 105 */ + .long sys_newstat + .long sys_newlstat + .long sys_newfstat + .long sys_uname + .long sys_ni_syscall /* 110 */ /* iopl */ + .long sys_vhangup + .long sys_ni_syscall /* idle */ + .long sys_ni_syscall /* vm86old */ + .long sys_wait4 + .long sys_swapoff /* 115 */ + .long sys_sysinfo + .long sys_ipc /* Obsolete ipc syscall implementation */ + .long sys_fsync + .long sys_sigreturn + .long sys_clone /* 120 */ + .long sys_setdomainname + .long sys_newuname + .long sys_ni_syscall /* sys_modify_ldt */ + .long sys_adjtimex + .long sys_mprotect /* 125 */ + .long sys_sigprocmask + .long sys_ni_syscall /* old "create_module" */ + .long sys_init_module + .long sys_delete_module + .long sys_ni_syscall /* 130: old "get_kernel_syms" */ + .long sys_quotactl + .long sys_getpgid + .long sys_fchdir + .long sys_bdflush + .long sys_sysfs /* 135 */ + .long sys_personality + .long sys_ni_syscall /* for afs_syscall */ + .long sys_setfsuid16 + .long sys_setfsgid16 + .long sys_llseek /* 140 */ + .long sys_getdents + .long sys_select + .long sys_flock + .long sys_msync + .long sys_readv /* 145 */ + .long sys_writev + .long sys_getsid + .long sys_fdatasync + .long sys_sysctl + .long sys_mlock /* 150 */ + .long sys_munlock + .long sys_mlockall + .long sys_munlockall + .long sys_sched_setparam + .long sys_sched_getparam /* 155 */ + .long sys_sched_setscheduler + .long sys_sched_getscheduler + .long sys_sched_yield + .long sys_sched_get_priority_max + .long sys_sched_get_priority_min /* 160 */ + .long sys_sched_rr_get_interval + .long sys_nanosleep + .long sys_mremap + .long sys_setresuid16 + .long sys_getresuid16 /* 165 */ + .long sys_ni_syscall /* vm86 */ + .long sys_ni_syscall /* old "query_module" */ + .long sys_poll + .long sys_nfsservctl + .long sys_setresgid16 /* 170 */ + .long sys_getresgid16 + .long sys_prctl + .long sys_rt_sigreturn + .long sys_rt_sigaction + .long sys_rt_sigprocmask /* 175 */ + .long sys_rt_sigpending + .long sys_rt_sigtimedwait + .long sys_rt_sigqueueinfo + .long sys_rt_sigsuspend + .long sys_pread64 /* 180 */ + .long sys_pwrite64 + .long sys_chown16 + .long sys_getcwd + .long sys_capget + .long sys_capset /* 185 */ + .long sys_sigaltstack + .long sys_sendfile + .long sys_ni_syscall /* streams1 */ + .long sys_ni_syscall /* streams2 */ + .long sys_vfork /* 190 */ + .long sys_getrlimit + .long sys_mmap2 + .long sys_truncate64 + .long sys_ftruncate64 + .long sys_stat64 /* 195 */ + .long sys_lstat64 + .long sys_fstat64 + .long sys_lchown + .long sys_getuid + .long sys_getgid /* 200 */ + .long sys_geteuid + .long sys_getegid + .long sys_setreuid + .long sys_setregid + .long sys_getgroups /* 205 */ + .long sys_setgroups + .long sys_fchown + .long sys_setresuid + .long sys_getresuid + .long sys_setresgid /* 210 */ + .long sys_getresgid + .long sys_chown + .long sys_setuid + .long sys_setgid + .long sys_setfsuid /* 215 */ + .long sys_setfsgid + .long sys_pivot_root + .long sys_mincore + .long sys_madvise + /* Broken-out socket family (maintain backwards compatibility in syscall + numbering with 2.4) */ + .long sys_socket /* 220 */ + .long sys_bind + .long sys_connect + .long sys_listen + .long sys_accept + .long sys_getsockname /* 225 */ + .long sys_getpeername + .long sys_socketpair + .long sys_send + .long sys_sendto + .long sys_recv /* 230*/ + .long sys_recvfrom + .long sys_shutdown + .long sys_setsockopt + .long sys_getsockopt + .long sys_sendmsg /* 235 */ + .long sys_recvmsg + /* Broken-out IPC family (maintain backwards compatibility in syscall + numbering with 2.4) */ + .long sys_semop + .long sys_semget + .long sys_semctl + .long sys_msgsnd /* 240 */ + .long sys_msgrcv + .long sys_msgget + .long sys_msgctl + .long sys_shmatcall + .long sys_shmdt /* 245 */ + .long sys_shmget + .long sys_shmctl + /* Rest of syscalls listed in 2.4 i386 unistd.h */ + .long sys_getdents64 + .long sys_fcntl64 + .long sys_ni_syscall /* 250 reserved for TUX */ + .long sys_ni_syscall /* Reserved for Security */ + .long sys_gettid + .long sys_readahead + .long sys_setxattr + .long sys_lsetxattr /* 255 */ + .long sys_fsetxattr + .long sys_getxattr + .long sys_lgetxattr + .long sys_fgetxattr + .long sys_listxattr /* 260 */ + .long sys_llistxattr + .long sys_flistxattr + .long sys_removexattr + .long sys_lremovexattr + .long sys_fremovexattr /* 265 */ + .long sys_tkill + .long sys_sendfile64 + .long sys_futex + .long sys_sched_setaffinity + .long sys_sched_getaffinity /* 270 */ + .long sys_ni_syscall + .long sys_ni_syscall + .long sys_io_setup + .long sys_io_destroy + .long sys_io_getevents /* 275 */ + .long sys_io_submit + .long sys_io_cancel + .long sys_fadvise64 + .long sys_ni_syscall + .long sys_exit_group /* 280 */ + /* Rest of new 2.6 syscalls */ + .long sys_lookup_dcookie + .long sys_epoll_create + .long sys_epoll_ctl + .long sys_epoll_wait + .long sys_remap_file_pages /* 285 */ + .long sys_set_tid_address + .long sys_timer_create + .long sys_timer_settime + .long sys_timer_gettime + .long sys_timer_getoverrun /* 290 */ + .long sys_timer_delete + .long sys_clock_settime + .long sys_clock_gettime + .long sys_clock_getres + .long sys_clock_nanosleep /* 295 */ + .long sys_statfs64 + .long sys_fstatfs64 + .long sys_tgkill + .long sys_utimes + .long sys_fadvise64_64 /* 300 */ + .long sys_ni_syscall /* Reserved for vserver */ + .long sys_ni_syscall /* Reserved for mbind */ + .long sys_ni_syscall /* get_mempolicy */ + .long sys_ni_syscall /* set_mempolicy */ + .long sys_mq_open /* 305 */ + .long sys_mq_unlink + .long sys_mq_timedsend + .long sys_mq_timedreceive + .long sys_mq_notify + .long sys_mq_getsetattr /* 310 */ + .long sys_ni_syscall /* Reserved for kexec */ + .long sys_waitid + .long sys_add_key + .long sys_request_key + .long sys_keyctl /* 315 */ + diff --git a/arch/sh64/kernel/time.c b/arch/sh64/kernel/time.c new file mode 100644 index 00000000000..6c84da3efc7 --- /dev/null +++ b/arch/sh64/kernel/time.c @@ -0,0 +1,610 @@ +/* + * 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. + * + * arch/sh64/kernel/time.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003, 2004 Paul Mundt + * Copyright (C) 2003 Richard Curnow + * + * Original TMU/RTC code taken from sh version. + * Copyright (C) 1999 Tetsuya Okada & Niibe Yutaka + * Some code taken from i386 version. + * Copyright (C) 1991, 1992, 1995 Linus Torvalds + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/profile.h> +#include <linux/smp.h> + +#include <asm/registers.h> /* required by inline __asm__ stmt. */ + +#include <asm/processor.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/delay.h> + +#include <linux/timex.h> +#include <linux/irq.h> +#include <asm/hardware.h> + +#define TMU_TOCR_INIT 0x00 +#define TMU0_TCR_INIT 0x0020 +#define TMU_TSTR_INIT 1 +#define TMU_TSTR_OFF 0 + +/* RCR1 Bits */ +#define RCR1_CF 0x80 /* Carry Flag */ +#define RCR1_CIE 0x10 /* Carry Interrupt Enable */ +#define RCR1_AIE 0x08 /* Alarm Interrupt Enable */ +#define RCR1_AF 0x01 /* Alarm Flag */ + +/* RCR2 Bits */ +#define RCR2_PEF 0x80 /* PEriodic interrupt Flag */ +#define RCR2_PESMASK 0x70 /* Periodic interrupt Set */ +#define RCR2_RTCEN 0x08 /* ENable RTC */ +#define RCR2_ADJ 0x04 /* ADJustment (30-second) */ +#define RCR2_RESET 0x02 /* Reset bit */ +#define RCR2_START 0x01 /* Start bit */ + +/* Clock, Power and Reset Controller */ +#define CPRC_BLOCK_OFF 0x01010000 +#define CPRC_BASE PHYS_PERIPHERAL_BLOCK + CPRC_BLOCK_OFF + +#define FRQCR (cprc_base+0x0) +#define WTCSR (cprc_base+0x0018) +#define STBCR (cprc_base+0x0030) + +/* Time Management Unit */ +#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) + +#define TMU_TOCR tmu_base+0x0 /* Byte access */ +#define TMU_TSTR tmu_base+0x4 /* Byte access */ + +#define TMU0_TCOR TMU0_BASE+0x0 /* Long access */ +#define TMU0_TCNT TMU0_BASE+0x4 /* Long access */ +#define TMU0_TCR TMU0_BASE+0x8 /* Word access */ + +/* Real Time Clock */ +#define RTC_BLOCK_OFF 0x01040000 +#define RTC_BASE PHYS_PERIPHERAL_BLOCK + RTC_BLOCK_OFF + +#define R64CNT rtc_base+0x00 +#define RSECCNT rtc_base+0x04 +#define RMINCNT rtc_base+0x08 +#define RHRCNT rtc_base+0x0c +#define RWKCNT rtc_base+0x10 +#define RDAYCNT rtc_base+0x14 +#define RMONCNT rtc_base+0x18 +#define RYRCNT rtc_base+0x1c /* 16bit */ +#define RSECAR rtc_base+0x20 +#define RMINAR rtc_base+0x24 +#define RHRAR rtc_base+0x28 +#define RWKAR rtc_base+0x2c +#define RDAYAR rtc_base+0x30 +#define RMONAR rtc_base+0x34 +#define RCR1 rtc_base+0x38 +#define RCR2 rtc_base+0x3c + +#ifndef BCD_TO_BIN +#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) +#endif + +#ifndef BIN_TO_BCD +#define BIN_TO_BCD(val) ((val)=(((val)/10)<<4) + (val)%10) +#endif + +#define TICK_SIZE (tick_nsec / 1000) + +extern unsigned long wall_jiffies; + +u64 jiffies_64 = INITIAL_JIFFIES; + +static unsigned long tmu_base, rtc_base; +unsigned long cprc_base; + +/* Variables to allow interpolation of time of day to resolution better than a + * jiffy. */ + +/* This is effectively protected by xtime_lock */ +static unsigned long ctc_last_interrupt; +static unsigned long long usecs_per_jiffy = 1000000/HZ; /* Approximation */ + +#define CTC_JIFFY_SCALE_SHIFT 40 + +/* 2**CTC_JIFFY_SCALE_SHIFT / ctc_ticks_per_jiffy */ +static unsigned long long scaled_recip_ctc_ticks_per_jiffy; + +/* Estimate number of microseconds that have elapsed since the last timer tick, + by scaling the delta that has occured in the CTC register. + + WARNING WARNING WARNING : This algorithm relies on the CTC decrementing at + the CPU clock rate. If the CPU sleeps, the CTC stops counting. Bear this + in mind if enabling SLEEP_WORKS in process.c. In that case, this algorithm + probably needs to use TMU.TCNT0 instead. This will work even if the CPU is + sleeping, though will be coarser. + + FIXME : What if usecs_per_tick is moving around too much, e.g. if an adjtime + is running or if the freq or tick arguments of adjtimex are modified after + we have calibrated the scaling factor? This will result in either a jump at + the end of a tick period, or a wrap backwards at the start of the next one, + if the application is reading the time of day often enough. I think we + ought to do better than this. For this reason, usecs_per_jiffy is left + separated out in the calculation below. This allows some future hook into + the adjtime-related stuff in kernel/timer.c to remove this hazard. + +*/ + +static unsigned long usecs_since_tick(void) +{ + unsigned long long current_ctc; + long ctc_ticks_since_interrupt; + unsigned long long ull_ctc_ticks_since_interrupt; + unsigned long result; + + unsigned long long mul1_out; + unsigned long long mul1_out_high; + unsigned long long mul2_out_low, mul2_out_high; + + /* Read CTC register */ + asm ("getcon cr62, %0" : "=r" (current_ctc)); + /* Note, the CTC counts down on each CPU clock, not up. + Note(2), use long type to get correct wraparound arithmetic when + the counter crosses zero. */ + ctc_ticks_since_interrupt = (long) ctc_last_interrupt - (long) current_ctc; + ull_ctc_ticks_since_interrupt = (unsigned long long) ctc_ticks_since_interrupt; + + /* Inline assembly to do 32x32x32->64 multiplier */ + asm volatile ("mulu.l %1, %2, %0" : + "=r" (mul1_out) : + "r" (ull_ctc_ticks_since_interrupt), "r" (usecs_per_jiffy)); + + mul1_out_high = mul1_out >> 32; + + asm volatile ("mulu.l %1, %2, %0" : + "=r" (mul2_out_low) : + "r" (mul1_out), "r" (scaled_recip_ctc_ticks_per_jiffy)); + +#if 1 + asm volatile ("mulu.l %1, %2, %0" : + "=r" (mul2_out_high) : + "r" (mul1_out_high), "r" (scaled_recip_ctc_ticks_per_jiffy)); +#endif + + result = (unsigned long) (((mul2_out_high << 32) + mul2_out_low) >> CTC_JIFFY_SCALE_SHIFT); + + return result; +} + +void do_gettimeofday(struct timeval *tv) +{ + unsigned long flags; + unsigned long seq; + unsigned long usec, sec; + + do { + seq = read_seqbegin_irqsave(&xtime_lock, flags); + usec = usecs_since_tick(); + { + unsigned long lost = jiffies - wall_jiffies; + + if (lost) + usec += lost * (1000000 / HZ); + } + + sec = xtime.tv_sec; + usec += xtime.tv_nsec / 1000; + } while (read_seqretry_irqrestore(&xtime_lock, seq, flags)); + + while (usec >= 1000000) { + usec -= 1000000; + sec++; + } + + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +int do_settimeofday(struct timespec *tv) +{ + time_t wtm_sec, sec = tv->tv_sec; + long wtm_nsec, nsec = tv->tv_nsec; + + if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + + write_seqlock_irq(&xtime_lock); + /* + * This is revolting. We need to set "xtime" correctly. However, the + * value in this location is the value at the most recent update of + * wall time. Discover what correction gettimeofday() would have + * made, and then undo it! + */ + nsec -= 1000 * (usecs_since_tick() + + (jiffies - wall_jiffies) * (1000000 / HZ)); + + wtm_sec = wall_to_monotonic.tv_sec + (xtime.tv_sec - sec); + wtm_nsec = wall_to_monotonic.tv_nsec + (xtime.tv_nsec - nsec); + + set_normalized_timespec(&xtime, sec, nsec); + set_normalized_timespec(&wall_to_monotonic, wtm_sec, wtm_nsec); + + time_adjust = 0; /* stop active adjtime() */ + time_status |= STA_UNSYNC; + time_maxerror = NTP_PHASE_LIMIT; + time_esterror = NTP_PHASE_LIMIT; + write_sequnlock_irq(&xtime_lock); + clock_was_set(); + + return 0; +} + +static int set_rtc_time(unsigned long nowtime) +{ + int retval = 0; + int real_seconds, real_minutes, cmos_minutes; + + ctrl_outb(RCR2_RESET, RCR2); /* Reset pre-scaler & stop RTC */ + + cmos_minutes = ctrl_inb(RMINCNT); + BCD_TO_BIN(cmos_minutes); + + /* + * since we're only adjusting minutes and seconds, + * don't interfere with hour overflow. This avoids + * messing with unknown time zones but requires your + * RTC not to be off by more than 15 minutes + */ + real_seconds = nowtime % 60; + real_minutes = nowtime / 60; + if (((abs(real_minutes - cmos_minutes) + 15)/30) & 1) + real_minutes += 30; /* correct for half hour time zone */ + real_minutes %= 60; + + if (abs(real_minutes - cmos_minutes) < 30) { + BIN_TO_BCD(real_seconds); + BIN_TO_BCD(real_minutes); + ctrl_outb(real_seconds, RSECCNT); + ctrl_outb(real_minutes, RMINCNT); + } else { + printk(KERN_WARNING + "set_rtc_time: can't update from %d to %d\n", + cmos_minutes, real_minutes); + retval = -1; + } + + ctrl_outb(RCR2_RTCEN|RCR2_START, RCR2); /* Start RTC */ + + return retval; +} + +/* last time the RTC clock got updated */ +static long last_rtc_update = 0; + +/* + * timer_interrupt() needs to keep up the real-time clock, + * as well as call the "do_timer()" routine every clocktick + */ +static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long long current_ctc; + asm ("getcon cr62, %0" : "=r" (current_ctc)); + ctc_last_interrupt = (unsigned long) current_ctc; + + do_timer(regs); +#ifndef CONFIG_SMP + update_process_times(user_mode(regs)); +#endif + profile_tick(CPU_PROFILING, regs); + +#ifdef CONFIG_HEARTBEAT + { + extern void heartbeat(void); + + heartbeat(); + } +#endif + + /* + * If we have an externally synchronized Linux clock, then update + * RTC clock accordingly every ~11 minutes. Set_rtc_mmss() has to be + * called as close as possible to 500 ms before the new second starts. + */ + if ((time_status & STA_UNSYNC) == 0 && + xtime.tv_sec > last_rtc_update + 660 && + (xtime.tv_nsec / 1000) >= 500000 - ((unsigned) TICK_SIZE) / 2 && + (xtime.tv_nsec / 1000) <= 500000 + ((unsigned) TICK_SIZE) / 2) { + if (set_rtc_time(xtime.tv_sec) == 0) + last_rtc_update = xtime.tv_sec; + else + last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */ + } +} + +/* + * This is the same as the above, except we _also_ save the current + * Time Stamp Counter value at the time of the timer interrupt, so that + * we later on can estimate the time of day more exactly. + */ +static irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long timer_status; + + /* Clear UNF bit */ + timer_status = ctrl_inw(TMU0_TCR); + timer_status &= ~0x100; + ctrl_outw(timer_status, TMU0_TCR); + + /* + * Here we are in the timer irq handler. We just have irqs locally + * disabled but we don't know if the timer_bh is running on the other + * CPU. We need to avoid to SMP race with it. NOTE: we don' t need + * the irq version of write_lock because as just said we have irq + * locally disabled. -arca + */ + write_lock(&xtime_lock); + do_timer_interrupt(irq, NULL, regs); + write_unlock(&xtime_lock); + + return IRQ_HANDLED; +} + +static unsigned long get_rtc_time(void) +{ + unsigned int sec, min, hr, wk, day, mon, yr, yr100; + + again: + do { + ctrl_outb(0, RCR1); /* Clear CF-bit */ + sec = ctrl_inb(RSECCNT); + min = ctrl_inb(RMINCNT); + hr = ctrl_inb(RHRCNT); + wk = ctrl_inb(RWKCNT); + day = ctrl_inb(RDAYCNT); + mon = ctrl_inb(RMONCNT); + yr = ctrl_inw(RYRCNT); + yr100 = (yr >> 8); + yr &= 0xff; + } while ((ctrl_inb(RCR1) & RCR1_CF) != 0); + + BCD_TO_BIN(yr100); + BCD_TO_BIN(yr); + BCD_TO_BIN(mon); + BCD_TO_BIN(day); + BCD_TO_BIN(hr); + BCD_TO_BIN(min); + BCD_TO_BIN(sec); + + if (yr > 99 || mon < 1 || mon > 12 || day > 31 || day < 1 || + hr > 23 || min > 59 || sec > 59) { + printk(KERN_ERR + "SH RTC: invalid value, resetting to 1 Jan 2000\n"); + ctrl_outb(RCR2_RESET, RCR2); /* Reset & Stop */ + ctrl_outb(0, RSECCNT); + ctrl_outb(0, RMINCNT); + ctrl_outb(0, RHRCNT); + ctrl_outb(6, RWKCNT); + ctrl_outb(1, RDAYCNT); + ctrl_outb(1, RMONCNT); + ctrl_outw(0x2000, RYRCNT); + ctrl_outb(RCR2_RTCEN|RCR2_START, RCR2); /* Start */ + goto again; + } + + return mktime(yr100 * 100 + yr, mon, day, hr, min, sec); +} + +static __init unsigned int get_cpu_hz(void) +{ + unsigned int count; + unsigned long __dummy; + unsigned long ctc_val_init, ctc_val; + + /* + ** Regardless the toolchain, force the compiler to use the + ** arbitrary register r3 as a clock tick counter. + ** NOTE: r3 must be in accordance with rtc_interrupt() + */ + register unsigned long long __rtc_irq_flag __asm__ ("r3"); + + local_irq_enable(); + do {} while (ctrl_inb(R64CNT) != 0); + ctrl_outb(RCR1_CIE, RCR1); /* Enable carry interrupt */ + + /* + * r3 is arbitrary. CDC does not support "=z". + */ + ctc_val_init = 0xffffffff; + ctc_val = ctc_val_init; + + asm volatile("gettr tr0, %1\n\t" + "putcon %0, " __CTC "\n\t" + "and %2, r63, %2\n\t" + "pta $+4, tr0\n\t" + "beq/l %2, r63, tr0\n\t" + "ptabs %1, tr0\n\t" + "getcon " __CTC ", %0\n\t" + : "=r"(ctc_val), "=r" (__dummy), "=r" (__rtc_irq_flag) + : "0" (0)); + local_irq_disable(); + /* + * SH-3: + * CPU clock = 4 stages * loop + * tst rm,rm if id ex + * bt/s 1b if id ex + * add #1,rd if id ex + * (if) pipe line stole + * tst rm,rm if id ex + * .... + * + * + * SH-4: + * CPU clock = 6 stages * loop + * I don't know why. + * .... + * + * SH-5: + * Use CTC register to count. This approach returns the right value + * even if the I-cache is disabled (e.g. whilst debugging.) + * + */ + + count = ctc_val_init - ctc_val; /* CTC counts down */ + +#if defined (CONFIG_SH_SIMULATOR) + /* + * Let's pretend we are a 5MHz SH-5 to avoid a too + * little timer interval. Also to keep delay + * calibration within a reasonable time. + */ + return 5000000; +#else + /* + * This really is count by the number of clock cycles + * by the ratio between a complete R64CNT + * wrap-around (128) and CUI interrupt being raised (64). + */ + return count*2; +#endif +} + +static irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + ctrl_outb(0, RCR1); /* Disable Carry Interrupts */ + regs->regs[3] = 1; /* Using r3 */ + + return IRQ_HANDLED; +} + +static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL}; +static struct irqaction irq1 = { rtc_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "rtc", NULL, NULL}; + +void __init time_init(void) +{ + unsigned int cpu_clock, master_clock, bus_clock, module_clock; + unsigned long interval; + unsigned long frqcr, ifc, pfc; + static int ifc_table[] = { 2, 4, 6, 8, 10, 12, 16, 24 }; +#define bfc_table ifc_table /* Same */ +#define pfc_table ifc_table /* Same */ + + tmu_base = onchip_remap(TMU_BASE, 1024, "TMU"); + if (!tmu_base) { + panic("Unable to remap TMU\n"); + } + + rtc_base = onchip_remap(RTC_BASE, 1024, "RTC"); + if (!rtc_base) { + panic("Unable to remap RTC\n"); + } + + cprc_base = onchip_remap(CPRC_BASE, 1024, "CPRC"); + if (!cprc_base) { + panic("Unable to remap CPRC\n"); + } + + xtime.tv_sec = get_rtc_time(); + xtime.tv_nsec = 0; + + setup_irq(TIMER_IRQ, &irq0); + setup_irq(RTC_IRQ, &irq1); + + /* Check how fast it is.. */ + cpu_clock = get_cpu_hz(); + + /* Note careful order of operations to maintain reasonable precision and avoid overflow. */ + scaled_recip_ctc_ticks_per_jiffy = ((1ULL << CTC_JIFFY_SCALE_SHIFT) / (unsigned long long)(cpu_clock / HZ)); + + disable_irq(RTC_IRQ); + + printk("CPU clock: %d.%02dMHz\n", + (cpu_clock / 1000000), (cpu_clock % 1000000)/10000); + { + unsigned short bfc; + frqcr = ctrl_inl(FRQCR); + ifc = ifc_table[(frqcr>> 6) & 0x0007]; + bfc = bfc_table[(frqcr>> 3) & 0x0007]; + pfc = pfc_table[(frqcr>> 12) & 0x0007]; + master_clock = cpu_clock * ifc; + bus_clock = master_clock/bfc; + } + + printk("Bus clock: %d.%02dMHz\n", + (bus_clock/1000000), (bus_clock % 1000000)/10000); + module_clock = master_clock/pfc; + printk("Module clock: %d.%02dMHz\n", + (module_clock/1000000), (module_clock % 1000000)/10000); + interval = (module_clock/(HZ*4)); + + printk("Interval = %ld\n", interval); + + current_cpu_data.cpu_clock = cpu_clock; + current_cpu_data.master_clock = master_clock; + current_cpu_data.bus_clock = bus_clock; + current_cpu_data.module_clock = module_clock; + + /* Start TMU0 */ + ctrl_outb(TMU_TSTR_OFF, TMU_TSTR); + ctrl_outb(TMU_TOCR_INIT, TMU_TOCR); + ctrl_outw(TMU0_TCR_INIT, TMU0_TCR); + ctrl_outl(interval, TMU0_TCOR); + ctrl_outl(interval, TMU0_TCNT); + ctrl_outb(TMU_TSTR_INIT, TMU_TSTR); +} + +void enter_deep_standby(void) +{ + /* Disable watchdog timer */ + ctrl_outl(0xa5000000, WTCSR); + /* Configure deep standby on sleep */ + ctrl_outl(0x03, STBCR); + +#ifdef CONFIG_SH_ALPHANUMERIC + { + extern void mach_alphanum(int position, unsigned char value); + extern void mach_alphanum_brightness(int setting); + char halted[] = "Halted. "; + int i; + mach_alphanum_brightness(6); /* dimmest setting above off */ + for (i=0; i<8; i++) { + mach_alphanum(i, halted[i]); + } + asm __volatile__ ("synco"); + } +#endif + + asm __volatile__ ("sleep"); + asm __volatile__ ("synci"); + asm __volatile__ ("nop"); + asm __volatile__ ("nop"); + asm __volatile__ ("nop"); + asm __volatile__ ("nop"); + panic("Unexpected wakeup!\n"); +} + +/* + * Scheduler clock - returns current time in nanosec units. + */ +unsigned long long sched_clock(void) +{ + return (unsigned long long)jiffies * (1000000000 / HZ); +} + diff --git a/arch/sh64/kernel/traps.c b/arch/sh64/kernel/traps.c new file mode 100644 index 00000000000..224b7f5b922 --- /dev/null +++ b/arch/sh64/kernel/traps.c @@ -0,0 +1,961 @@ +/* + * 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. + * + * arch/sh64/kernel/traps.c + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * Copyright (C) 2003, 2004 Paul Mundt + * Copyright (C) 2003, 2004 Richard Curnow + * + */ + +/* + * 'Traps.c' handles hardware traps and faults after we have saved some + * state in 'entry.S'. + */ +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/smp_lock.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/kallsyms.h> +#include <linux/interrupt.h> +#include <linux/sysctl.h> +#include <linux/module.h> + +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/atomic.h> +#include <asm/processor.h> +#include <asm/pgtable.h> + +#undef DEBUG_EXCEPTION +#ifdef DEBUG_EXCEPTION +/* implemented in ../lib/dbg.c */ +extern void show_excp_regs(char *fname, int trapnr, int signr, + struct pt_regs *regs); +#else +#define show_excp_regs(a, b, c, d) +#endif + +static void do_unhandled_exception(int trapnr, int signr, char *str, char *fn_name, + unsigned long error_code, struct pt_regs *regs, struct task_struct *tsk); + +#define DO_ERROR(trapnr, signr, str, name, tsk) \ +asmlinkage void do_##name(unsigned long error_code, struct pt_regs *regs) \ +{ \ + do_unhandled_exception(trapnr, signr, str, __stringify(name), error_code, regs, current); \ +} + +spinlock_t die_lock; + +void die(const char * str, struct pt_regs * regs, long err) +{ + console_verbose(); + spin_lock_irq(&die_lock); + printk("%s: %lx\n", str, (err & 0xffffff)); + show_regs(regs); + spin_unlock_irq(&die_lock); + do_exit(SIGSEGV); +} + +static inline void die_if_kernel(const char * str, struct pt_regs * regs, long err) +{ + if (!user_mode(regs)) + die(str, regs, err); +} + +static void die_if_no_fixup(const char * str, struct pt_regs * regs, long err) +{ + if (!user_mode(regs)) { + const struct exception_table_entry *fixup; + fixup = search_exception_tables(regs->pc); + if (fixup) { + regs->pc = fixup->fixup; + return; + } + die(str, regs, err); + } +} + +DO_ERROR(13, SIGILL, "illegal slot instruction", illegal_slot_inst, current) +DO_ERROR(87, SIGSEGV, "address error (exec)", address_error_exec, current) + + +/* Implement misaligned load/store handling for kernel (and optionally for user + mode too). Limitation : only SHmedia mode code is handled - there is no + handling at all for misaligned accesses occurring in SHcompact code yet. */ + +static int misaligned_fixup(struct pt_regs *regs); + +asmlinkage void do_address_error_load(unsigned long error_code, struct pt_regs *regs) +{ + if (misaligned_fixup(regs) < 0) { + do_unhandled_exception(7, SIGSEGV, "address error(load)", + "do_address_error_load", + error_code, regs, current); + } + return; +} + +asmlinkage void do_address_error_store(unsigned long error_code, struct pt_regs *regs) +{ + if (misaligned_fixup(regs) < 0) { + do_unhandled_exception(8, SIGSEGV, "address error(store)", + "do_address_error_store", + error_code, regs, current); + } + return; +} + +#if defined(CONFIG_SH64_ID2815_WORKAROUND) + +#define OPCODE_INVALID 0 +#define OPCODE_USER_VALID 1 +#define OPCODE_PRIV_VALID 2 + +/* getcon/putcon - requires checking which control register is referenced. */ +#define OPCODE_CTRL_REG 3 + +/* Table of valid opcodes for SHmedia mode. + Form a 10-bit value by concatenating the major/minor opcodes i.e. + opcode[31:26,20:16]. The 6 MSBs of this value index into the following + array. The 4 LSBs select the bit-pair in the entry (bits 1:0 correspond to + LSBs==4'b0000 etc). */ +static unsigned long shmedia_opcode_table[64] = { + 0x55554044,0x54445055,0x15141514,0x14541414,0x00000000,0x10001000,0x01110055,0x04050015, + 0x00000444,0xc0000000,0x44545515,0x40405555,0x55550015,0x10005555,0x55555505,0x04050000, + 0x00000555,0x00000404,0x00040445,0x15151414,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000055,0x40404444,0x00000404,0xc0009495,0x00000000,0x00000000,0x00000000,0x00000000, + 0x55555555,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555, + 0x55555555,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555, + 0x80005050,0x04005055,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555,0x55555555, + 0x81055554,0x00000404,0x55555555,0x55555555,0x00000000,0x00000000,0x00000000,0x00000000 +}; + +void do_reserved_inst(unsigned long error_code, struct pt_regs *regs) +{ + /* Workaround SH5-101 cut2 silicon defect #2815 : + in some situations, inter-mode branches from SHcompact -> SHmedia + which should take ITLBMISS or EXECPROT exceptions at the target + falsely take RESINST at the target instead. */ + + unsigned long opcode = 0x6ff4fff0; /* guaranteed reserved opcode */ + unsigned long pc, aligned_pc; + int get_user_error; + int trapnr = 12; + int signr = SIGILL; + char *exception_name = "reserved_instruction"; + + pc = regs->pc; + if ((pc & 3) == 1) { + /* SHmedia : check for defect. This requires executable vmas + to be readable too. */ + aligned_pc = pc & ~3; + if (!access_ok(VERIFY_READ, aligned_pc, sizeof(unsigned long))) { + get_user_error = -EFAULT; + } else { + get_user_error = __get_user(opcode, (unsigned long *)aligned_pc); + } + if (get_user_error >= 0) { + unsigned long index, shift; + unsigned long major, minor, combined; + unsigned long reserved_field; + reserved_field = opcode & 0xf; /* These bits are currently reserved as zero in all valid opcodes */ + major = (opcode >> 26) & 0x3f; + minor = (opcode >> 16) & 0xf; + combined = (major << 4) | minor; + index = major; + shift = minor << 1; + if (reserved_field == 0) { + int opcode_state = (shmedia_opcode_table[index] >> shift) & 0x3; + switch (opcode_state) { + case OPCODE_INVALID: + /* Trap. */ + break; + case OPCODE_USER_VALID: + /* Restart the instruction : the branch to the instruction will now be from an RTE + not from SHcompact so the silicon defect won't be triggered. */ + return; + case OPCODE_PRIV_VALID: + if (!user_mode(regs)) { + /* Should only ever get here if a module has + SHcompact code inside it. If so, the same fix up is needed. */ + return; /* same reason */ + } + /* Otherwise, user mode trying to execute a privileged instruction - + fall through to trap. */ + break; + case OPCODE_CTRL_REG: + /* If in privileged mode, return as above. */ + if (!user_mode(regs)) return; + /* In user mode ... */ + if (combined == 0x9f) { /* GETCON */ + unsigned long regno = (opcode >> 20) & 0x3f; + if (regno >= 62) { + return; + } + /* Otherwise, reserved or privileged control register, => trap */ + } else if (combined == 0x1bf) { /* PUTCON */ + unsigned long regno = (opcode >> 4) & 0x3f; + if (regno >= 62) { + return; + } + /* Otherwise, reserved or privileged control register, => trap */ + } else { + /* Trap */ + } + break; + default: + /* Fall through to trap. */ + break; + } + } + /* fall through to normal resinst processing */ + } else { + /* Error trying to read opcode. This typically means a + real fault, not a RESINST any more. So change the + codes. */ + trapnr = 87; + exception_name = "address error (exec)"; + signr = SIGSEGV; + } + } + + do_unhandled_exception(trapnr, signr, exception_name, "do_reserved_inst", error_code, regs, current); +} + +#else /* CONFIG_SH64_ID2815_WORKAROUND */ + +/* If the workaround isn't needed, this is just a straightforward reserved + instruction */ +DO_ERROR(12, SIGILL, "reserved instruction", reserved_inst, current) + +#endif /* CONFIG_SH64_ID2815_WORKAROUND */ + + +#include <asm/system.h> + +/* Called with interrupts disabled */ +asmlinkage void do_exception_error(unsigned long ex, struct pt_regs *regs) +{ + PLS(); + show_excp_regs(__FUNCTION__, -1, -1, regs); + die_if_kernel("exception", regs, ex); +} + +int do_unknown_trapa(unsigned long scId, struct pt_regs *regs) +{ + /* Syscall debug */ + printk("System call ID error: [0x1#args:8 #syscall:16 0x%lx]\n", scId); + + die_if_kernel("unknown trapa", regs, scId); + + return -ENOSYS; +} + +void show_stack(struct task_struct *tsk, unsigned long *sp) +{ +#ifdef CONFIG_KALLSYMS + extern void sh64_unwind(struct pt_regs *regs); + struct pt_regs *regs; + + regs = tsk ? tsk->thread.kregs : NULL; + + sh64_unwind(regs); +#else + printk(KERN_ERR "Can't backtrace on sh64 without CONFIG_KALLSYMS\n"); +#endif +} + +void show_task(unsigned long *sp) +{ + show_stack(NULL, sp); +} + +void dump_stack(void) +{ + show_task(NULL); +} +/* Needed by any user of WARN_ON in view of the defn in include/asm-sh/bug.h */ +EXPORT_SYMBOL(dump_stack); + +static void do_unhandled_exception(int trapnr, int signr, char *str, char *fn_name, + unsigned long error_code, struct pt_regs *regs, struct task_struct *tsk) +{ + show_excp_regs(fn_name, trapnr, signr, regs); + tsk->thread.error_code = error_code; + tsk->thread.trap_no = trapnr; + + if (user_mode(regs)) + force_sig(signr, tsk); + + die_if_no_fixup(str, regs, error_code); +} + +static int read_opcode(unsigned long long pc, unsigned long *result_opcode, int from_user_mode) +{ + int get_user_error; + unsigned long aligned_pc; + unsigned long opcode; + + if ((pc & 3) == 1) { + /* SHmedia */ + aligned_pc = pc & ~3; + if (from_user_mode) { + if (!access_ok(VERIFY_READ, aligned_pc, sizeof(unsigned long))) { + get_user_error = -EFAULT; + } else { + get_user_error = __get_user(opcode, (unsigned long *)aligned_pc); + *result_opcode = opcode; + } + return get_user_error; + } else { + /* If the fault was in the kernel, we can either read + * this directly, or if not, we fault. + */ + *result_opcode = *(unsigned long *) aligned_pc; + return 0; + } + } else if ((pc & 1) == 0) { + /* SHcompact */ + /* TODO : provide handling for this. We don't really support + user-mode SHcompact yet, and for a kernel fault, this would + have to come from a module built for SHcompact. */ + return -EFAULT; + } else { + /* misaligned */ + return -EFAULT; + } +} + +static int address_is_sign_extended(__u64 a) +{ + __u64 b; +#if (NEFF == 32) + b = (__u64)(__s64)(__s32)(a & 0xffffffffUL); + return (b == a) ? 1 : 0; +#else +#error "Sign extend check only works for NEFF==32" +#endif +} + +static int generate_and_check_address(struct pt_regs *regs, + __u32 opcode, + int displacement_not_indexed, + int width_shift, + __u64 *address) +{ + /* return -1 for fault, 0 for OK */ + + __u64 base_address, addr; + int basereg; + + basereg = (opcode >> 20) & 0x3f; + base_address = regs->regs[basereg]; + if (displacement_not_indexed) { + __s64 displacement; + displacement = (opcode >> 10) & 0x3ff; + displacement = ((displacement << 54) >> 54); /* sign extend */ + addr = (__u64)((__s64)base_address + (displacement << width_shift)); + } else { + __u64 offset; + int offsetreg; + offsetreg = (opcode >> 10) & 0x3f; + offset = regs->regs[offsetreg]; + addr = base_address + offset; + } + + /* Check sign extended */ + if (!address_is_sign_extended(addr)) { + return -1; + } + +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) + /* Check accessible. For misaligned access in the kernel, assume the + address is always accessible (and if not, just fault when the + load/store gets done.) */ + if (user_mode(regs)) { + if (addr >= TASK_SIZE) { + return -1; + } + /* Do access_ok check later - it depends on whether it's a load or a store. */ + } +#endif + + *address = addr; + return 0; +} + +/* Default value as for sh */ +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) +static int user_mode_unaligned_fixup_count = 10; +static int user_mode_unaligned_fixup_enable = 1; +#endif + +static int kernel_mode_unaligned_fixup_count = 32; + +static void misaligned_kernel_word_load(__u64 address, int do_sign_extend, __u64 *result) +{ + unsigned short x; + unsigned char *p, *q; + p = (unsigned char *) (int) address; + q = (unsigned char *) &x; + q[0] = p[0]; + q[1] = p[1]; + + if (do_sign_extend) { + *result = (__u64)(__s64) *(short *) &x; + } else { + *result = (__u64) x; + } +} + +static void misaligned_kernel_word_store(__u64 address, __u64 value) +{ + unsigned short x; + unsigned char *p, *q; + p = (unsigned char *) (int) address; + q = (unsigned char *) &x; + + x = (__u16) value; + p[0] = q[0]; + p[1] = q[1]; +} + +static int misaligned_load(struct pt_regs *regs, + __u32 opcode, + int displacement_not_indexed, + int width_shift, + int do_sign_extend) +{ + /* Return -1 for a fault, 0 for OK */ + int error; + int destreg; + __u64 address; + + error = generate_and_check_address(regs, opcode, + displacement_not_indexed, width_shift, &address); + if (error < 0) { + return error; + } + + destreg = (opcode >> 4) & 0x3f; +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) + if (user_mode(regs)) { + __u64 buffer; + + if (!access_ok(VERIFY_READ, (unsigned long) address, 1UL<<width_shift)) { + return -1; + } + + if (__copy_user(&buffer, (const void *)(int)address, (1 << width_shift)) > 0) { + return -1; /* fault */ + } + switch (width_shift) { + case 1: + if (do_sign_extend) { + regs->regs[destreg] = (__u64)(__s64) *(__s16 *) &buffer; + } else { + regs->regs[destreg] = (__u64) *(__u16 *) &buffer; + } + break; + case 2: + regs->regs[destreg] = (__u64)(__s64) *(__s32 *) &buffer; + break; + case 3: + regs->regs[destreg] = buffer; + break; + default: + printk("Unexpected width_shift %d in misaligned_load, PC=%08lx\n", + width_shift, (unsigned long) regs->pc); + break; + } + } else +#endif + { + /* kernel mode - we can take short cuts since if we fault, it's a genuine bug */ + __u64 lo, hi; + + switch (width_shift) { + case 1: + misaligned_kernel_word_load(address, do_sign_extend, ®s->regs[destreg]); + break; + case 2: + asm ("ldlo.l %1, 0, %0" : "=r" (lo) : "r" (address)); + asm ("ldhi.l %1, 3, %0" : "=r" (hi) : "r" (address)); + regs->regs[destreg] = lo | hi; + break; + case 3: + asm ("ldlo.q %1, 0, %0" : "=r" (lo) : "r" (address)); + asm ("ldhi.q %1, 7, %0" : "=r" (hi) : "r" (address)); + regs->regs[destreg] = lo | hi; + break; + + default: + printk("Unexpected width_shift %d in misaligned_load, PC=%08lx\n", + width_shift, (unsigned long) regs->pc); + break; + } + } + + return 0; + +} + +static int misaligned_store(struct pt_regs *regs, + __u32 opcode, + int displacement_not_indexed, + int width_shift) +{ + /* Return -1 for a fault, 0 for OK */ + int error; + int srcreg; + __u64 address; + + error = generate_and_check_address(regs, opcode, + displacement_not_indexed, width_shift, &address); + if (error < 0) { + return error; + } + + srcreg = (opcode >> 4) & 0x3f; +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) + if (user_mode(regs)) { + __u64 buffer; + + if (!access_ok(VERIFY_WRITE, (unsigned long) address, 1UL<<width_shift)) { + return -1; + } + + switch (width_shift) { + case 1: + *(__u16 *) &buffer = (__u16) regs->regs[srcreg]; + break; + case 2: + *(__u32 *) &buffer = (__u32) regs->regs[srcreg]; + break; + case 3: + buffer = regs->regs[srcreg]; + break; + default: + printk("Unexpected width_shift %d in misaligned_store, PC=%08lx\n", + width_shift, (unsigned long) regs->pc); + break; + } + + if (__copy_user((void *)(int)address, &buffer, (1 << width_shift)) > 0) { + return -1; /* fault */ + } + } else +#endif + { + /* kernel mode - we can take short cuts since if we fault, it's a genuine bug */ + __u64 val = regs->regs[srcreg]; + + switch (width_shift) { + case 1: + misaligned_kernel_word_store(address, val); + break; + case 2: + asm ("stlo.l %1, 0, %0" : : "r" (val), "r" (address)); + asm ("sthi.l %1, 3, %0" : : "r" (val), "r" (address)); + break; + case 3: + asm ("stlo.q %1, 0, %0" : : "r" (val), "r" (address)); + asm ("sthi.q %1, 7, %0" : : "r" (val), "r" (address)); + break; + + default: + printk("Unexpected width_shift %d in misaligned_store, PC=%08lx\n", + width_shift, (unsigned long) regs->pc); + break; + } + } + + return 0; + +} + +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) +/* Never need to fix up misaligned FPU accesses within the kernel since that's a real + error. */ +static int misaligned_fpu_load(struct pt_regs *regs, + __u32 opcode, + int displacement_not_indexed, + int width_shift, + int do_paired_load) +{ + /* Return -1 for a fault, 0 for OK */ + int error; + int destreg; + __u64 address; + + error = generate_and_check_address(regs, opcode, + displacement_not_indexed, width_shift, &address); + if (error < 0) { + return error; + } + + destreg = (opcode >> 4) & 0x3f; + if (user_mode(regs)) { + __u64 buffer; + __u32 buflo, bufhi; + + if (!access_ok(VERIFY_READ, (unsigned long) address, 1UL<<width_shift)) { + return -1; + } + + if (__copy_user(&buffer, (const void *)(int)address, (1 << width_shift)) > 0) { + return -1; /* fault */ + } + /* 'current' may be the current owner of the FPU state, so + context switch the registers into memory so they can be + indexed by register number. */ + if (last_task_used_math == current) { + grab_fpu(); + fpsave(¤t->thread.fpu.hard); + release_fpu(); + last_task_used_math = NULL; + regs->sr |= SR_FD; + } + + buflo = *(__u32*) &buffer; + bufhi = *(1 + (__u32*) &buffer); + + switch (width_shift) { + case 2: + current->thread.fpu.hard.fp_regs[destreg] = buflo; + break; + case 3: + if (do_paired_load) { + current->thread.fpu.hard.fp_regs[destreg] = buflo; + current->thread.fpu.hard.fp_regs[destreg+1] = bufhi; + } else { +#if defined(CONFIG_LITTLE_ENDIAN) + current->thread.fpu.hard.fp_regs[destreg] = bufhi; + current->thread.fpu.hard.fp_regs[destreg+1] = buflo; +#else + current->thread.fpu.hard.fp_regs[destreg] = buflo; + current->thread.fpu.hard.fp_regs[destreg+1] = bufhi; +#endif + } + break; + default: + printk("Unexpected width_shift %d in misaligned_fpu_load, PC=%08lx\n", + width_shift, (unsigned long) regs->pc); + break; + } + return 0; + } else { + die ("Misaligned FPU load inside kernel", regs, 0); + return -1; + } + + +} + +static int misaligned_fpu_store(struct pt_regs *regs, + __u32 opcode, + int displacement_not_indexed, + int width_shift, + int do_paired_load) +{ + /* Return -1 for a fault, 0 for OK */ + int error; + int srcreg; + __u64 address; + + error = generate_and_check_address(regs, opcode, + displacement_not_indexed, width_shift, &address); + if (error < 0) { + return error; + } + + srcreg = (opcode >> 4) & 0x3f; + if (user_mode(regs)) { + __u64 buffer; + /* Initialise these to NaNs. */ + __u32 buflo=0xffffffffUL, bufhi=0xffffffffUL; + + if (!access_ok(VERIFY_WRITE, (unsigned long) address, 1UL<<width_shift)) { + return -1; + } + + /* 'current' may be the current owner of the FPU state, so + context switch the registers into memory so they can be + indexed by register number. */ + if (last_task_used_math == current) { + grab_fpu(); + fpsave(¤t->thread.fpu.hard); + release_fpu(); + last_task_used_math = NULL; + regs->sr |= SR_FD; + } + + switch (width_shift) { + case 2: + buflo = current->thread.fpu.hard.fp_regs[srcreg]; + break; + case 3: + if (do_paired_load) { + buflo = current->thread.fpu.hard.fp_regs[srcreg]; + bufhi = current->thread.fpu.hard.fp_regs[srcreg+1]; + } else { +#if defined(CONFIG_LITTLE_ENDIAN) + bufhi = current->thread.fpu.hard.fp_regs[srcreg]; + buflo = current->thread.fpu.hard.fp_regs[srcreg+1]; +#else + buflo = current->thread.fpu.hard.fp_regs[srcreg]; + bufhi = current->thread.fpu.hard.fp_regs[srcreg+1]; +#endif + } + break; + default: + printk("Unexpected width_shift %d in misaligned_fpu_store, PC=%08lx\n", + width_shift, (unsigned long) regs->pc); + break; + } + + *(__u32*) &buffer = buflo; + *(1 + (__u32*) &buffer) = bufhi; + if (__copy_user((void *)(int)address, &buffer, (1 << width_shift)) > 0) { + return -1; /* fault */ + } + return 0; + } else { + die ("Misaligned FPU load inside kernel", regs, 0); + return -1; + } +} +#endif + +static int misaligned_fixup(struct pt_regs *regs) +{ + unsigned long opcode; + int error; + int major, minor; + +#if !defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) + /* Never fixup user mode misaligned accesses without this option enabled. */ + return -1; +#else + if (!user_mode_unaligned_fixup_enable) return -1; +#endif + + error = read_opcode(regs->pc, &opcode, user_mode(regs)); + if (error < 0) { + return error; + } + major = (opcode >> 26) & 0x3f; + minor = (opcode >> 16) & 0xf; + +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) + if (user_mode(regs) && (user_mode_unaligned_fixup_count > 0)) { + --user_mode_unaligned_fixup_count; + /* Only do 'count' worth of these reports, to remove a potential DoS against syslog */ + printk("Fixing up unaligned userspace access in \"%s\" pid=%d pc=0x%08x ins=0x%08lx\n", + current->comm, current->pid, (__u32)regs->pc, opcode); + } else +#endif + if (!user_mode(regs) && (kernel_mode_unaligned_fixup_count > 0)) { + --kernel_mode_unaligned_fixup_count; + if (in_interrupt()) { + printk("Fixing up unaligned kernelspace access in interrupt pc=0x%08x ins=0x%08lx\n", + (__u32)regs->pc, opcode); + } else { + printk("Fixing up unaligned kernelspace access in \"%s\" pid=%d pc=0x%08x ins=0x%08lx\n", + current->comm, current->pid, (__u32)regs->pc, opcode); + } + } + + + switch (major) { + case (0x84>>2): /* LD.W */ + error = misaligned_load(regs, opcode, 1, 1, 1); + break; + case (0xb0>>2): /* LD.UW */ + error = misaligned_load(regs, opcode, 1, 1, 0); + break; + case (0x88>>2): /* LD.L */ + error = misaligned_load(regs, opcode, 1, 2, 1); + break; + case (0x8c>>2): /* LD.Q */ + error = misaligned_load(regs, opcode, 1, 3, 0); + break; + + case (0xa4>>2): /* ST.W */ + error = misaligned_store(regs, opcode, 1, 1); + break; + case (0xa8>>2): /* ST.L */ + error = misaligned_store(regs, opcode, 1, 2); + break; + case (0xac>>2): /* ST.Q */ + error = misaligned_store(regs, opcode, 1, 3); + break; + + case (0x40>>2): /* indexed loads */ + switch (minor) { + case 0x1: /* LDX.W */ + error = misaligned_load(regs, opcode, 0, 1, 1); + break; + case 0x5: /* LDX.UW */ + error = misaligned_load(regs, opcode, 0, 1, 0); + break; + case 0x2: /* LDX.L */ + error = misaligned_load(regs, opcode, 0, 2, 1); + break; + case 0x3: /* LDX.Q */ + error = misaligned_load(regs, opcode, 0, 3, 0); + break; + default: + error = -1; + break; + } + break; + + case (0x60>>2): /* indexed stores */ + switch (minor) { + case 0x1: /* STX.W */ + error = misaligned_store(regs, opcode, 0, 1); + break; + case 0x2: /* STX.L */ + error = misaligned_store(regs, opcode, 0, 2); + break; + case 0x3: /* STX.Q */ + error = misaligned_store(regs, opcode, 0, 3); + break; + default: + error = -1; + break; + } + break; + +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) + case (0x94>>2): /* FLD.S */ + error = misaligned_fpu_load(regs, opcode, 1, 2, 0); + break; + case (0x98>>2): /* FLD.P */ + error = misaligned_fpu_load(regs, opcode, 1, 3, 1); + break; + case (0x9c>>2): /* FLD.D */ + error = misaligned_fpu_load(regs, opcode, 1, 3, 0); + break; + case (0x1c>>2): /* floating indexed loads */ + switch (minor) { + case 0x8: /* FLDX.S */ + error = misaligned_fpu_load(regs, opcode, 0, 2, 0); + break; + case 0xd: /* FLDX.P */ + error = misaligned_fpu_load(regs, opcode, 0, 3, 1); + break; + case 0x9: /* FLDX.D */ + error = misaligned_fpu_load(regs, opcode, 0, 3, 0); + break; + default: + error = -1; + break; + } + break; + case (0xb4>>2): /* FLD.S */ + error = misaligned_fpu_store(regs, opcode, 1, 2, 0); + break; + case (0xb8>>2): /* FLD.P */ + error = misaligned_fpu_store(regs, opcode, 1, 3, 1); + break; + case (0xbc>>2): /* FLD.D */ + error = misaligned_fpu_store(regs, opcode, 1, 3, 0); + break; + case (0x3c>>2): /* floating indexed stores */ + switch (minor) { + case 0x8: /* FSTX.S */ + error = misaligned_fpu_store(regs, opcode, 0, 2, 0); + break; + case 0xd: /* FSTX.P */ + error = misaligned_fpu_store(regs, opcode, 0, 3, 1); + break; + case 0x9: /* FSTX.D */ + error = misaligned_fpu_store(regs, opcode, 0, 3, 0); + break; + default: + error = -1; + break; + } + break; +#endif + + default: + /* Fault */ + error = -1; + break; + } + + if (error < 0) { + return error; + } else { + regs->pc += 4; /* Skip the instruction that's just been emulated */ + return 0; + } + +} + +static ctl_table unaligned_table[] = { + {1, "kernel_reports", &kernel_mode_unaligned_fixup_count, + sizeof(int), 0644, NULL, &proc_dointvec}, +#if defined(CONFIG_SH64_USER_MISALIGNED_FIXUP) + {2, "user_reports", &user_mode_unaligned_fixup_count, + sizeof(int), 0644, NULL, &proc_dointvec}, + {3, "user_enable", &user_mode_unaligned_fixup_enable, + sizeof(int), 0644, NULL, &proc_dointvec}, +#endif + {0} +}; + +static ctl_table unaligned_root[] = { + {1, "unaligned_fixup", NULL, 0, 0555, unaligned_table}, + {0} +}; + +static ctl_table sh64_root[] = { + {1, "sh64", NULL, 0, 0555, unaligned_root}, + {0} +}; +static struct ctl_table_header *sysctl_header; +static int __init init_sysctl(void) +{ + sysctl_header = register_sysctl_table(sh64_root, 0); + return 0; +} + +__initcall(init_sysctl); + + +asmlinkage void do_debug_interrupt(unsigned long code, struct pt_regs *regs) +{ + u64 peek_real_address_q(u64 addr); + u64 poke_real_address_q(u64 addr, u64 val); + unsigned long long DM_EXP_CAUSE_PHY = 0x0c100010; + unsigned long long exp_cause; + /* It's not worth ioremapping the debug module registers for the amount + of access we make to them - just go direct to their physical + addresses. */ + exp_cause = peek_real_address_q(DM_EXP_CAUSE_PHY); + if (exp_cause & ~4) { + printk("DM.EXP_CAUSE had unexpected bits set (=%08lx)\n", + (unsigned long)(exp_cause & 0xffffffff)); + } + show_state(); + /* Clear all DEBUGINT causes */ + poke_real_address_q(DM_EXP_CAUSE_PHY, 0x0); +} + diff --git a/arch/sh64/kernel/unwind.c b/arch/sh64/kernel/unwind.c new file mode 100644 index 00000000000..f934f97f9f9 --- /dev/null +++ b/arch/sh64/kernel/unwind.c @@ -0,0 +1,326 @@ +/* + * arch/sh64/kernel/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> + +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 *modname, namebuf[128]; + unsigned long offset, size; + 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, &size, &offset, &modname, 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: ", __FUNCTION__, + 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 sh64_unwind + * 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(struct pt_regs *regs); + +static void unwind_nested (unsigned long pc, unsigned long fp) +{ + if ((fp >= __MEMORY_START) && + ((fp & 7) == 0)) { + sh64_unwind_inner((struct pt_regs *) fp); + } +} + +static void sh64_unwind_inner(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(pc,fp); + return; + } + + if (pc == ((unsigned long) &ret_from_syscall & ~1)) { + printk("SYSCALL (PREEMPTED)\n"); + unwind_nested(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(pc,fp); + return; + } + + if (pc == ((unsigned long) &ret_from_irq & ~1)) { + printk("IRQ\n"); + unwind_nested(pc,fp); + return; + } + + cond = ((pc >= __MEMORY_START) && (fp >= __MEMORY_START) && + ((pc & 3) == 0) && ((fp & 7) == 0)); + + pc -= ofs; + + printk("[<%08lx>] ", pc); + print_symbol("%s\n", pc); + + 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"); + +} + +void sh64_unwind(struct pt_regs *regs) +{ + 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) + ); + } + + printk("\nCall Trace:\n"); + sh64_unwind_inner(regs); +} + diff --git a/arch/sh64/kernel/vmlinux.lds.S b/arch/sh64/kernel/vmlinux.lds.S new file mode 100644 index 00000000000..7d9f7a6339a --- /dev/null +++ b/arch/sh64/kernel/vmlinux.lds.S @@ -0,0 +1,181 @@ +/* + * 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. + * + * arch/sh5/vmlinux.lds.S + * + * ld script to make ST50 Linux kernel + * + * Copyright (C) 2000, 2001 Paolo Alberelli + * + * benedict.gaster@superh.com: 2nd May 2002 + * Add definition of empty_zero_page to be the first page of kernel image. + * + * benedict.gaster@superh.com: 3rd May 2002 + * Added support for ramdisk, removing statically linked romfs at the same time. + * + * lethal@linux-sh.org: 9th May 2003 + * Kill off GLOBAL_NAME() usage and other CDC-isms. + * + * lethal@linux-sh.org: 19th May 2003 + * Remove support for ancient toolchains. + */ + +#include <linux/config.h> +#include <asm/page.h> +#include <asm/cache.h> +#include <asm/processor.h> +#include <asm/thread_info.h> + +#define LOAD_OFFSET CONFIG_CACHED_MEMORY_OFFSET +#include <asm-generic/vmlinux.lds.h> + +#ifdef NOTDEF +#ifdef CONFIG_LITTLE_ENDIAN +OUTPUT_FORMAT("elf32-sh64l-linux", "elf32-sh64l-linux", "elf32-sh64l-linux") +#else +OUTPUT_FORMAT("elf32-sh64", "elf32-sh64", "elf32-sh64") +#endif +#endif + +OUTPUT_ARCH(sh:sh5) + +#define C_PHYS(x) AT (ADDR(x) - LOAD_OFFSET) + +ENTRY(__start) +SECTIONS +{ + . = CONFIG_CACHED_MEMORY_OFFSET + CONFIG_MEMORY_START + PAGE_SIZE; + _text = .; /* Text and read-only data */ + text = .; /* Text and read-only data */ + + .empty_zero_page : C_PHYS(.empty_zero_page) { + *(.empty_zero_page) + } = 0 + + .text : C_PHYS(.text) { + *(.text) + *(.text64) + *(.text..SHmedia32) + SCHED_TEXT + LOCK_TEXT + *(.fixup) + *(.gnu.warning) +#ifdef CONFIG_LITTLE_ENDIAN + } = 0x6ff0fff0 +#else + } = 0xf0fff06f +#endif + + /* We likely want __ex_table to be Cache Line aligned */ + . = ALIGN(L1_CACHE_BYTES); /* Exception table */ + __start___ex_table = .; + __ex_table : C_PHYS(__ex_table) { *(__ex_table) } + __stop___ex_table = .; + + RODATA + + _etext = .; /* End of text section */ + + .data : C_PHYS(.data) { /* Data */ + *(.data) + CONSTRUCTORS + } + + . = ALIGN(PAGE_SIZE); + .data.page_aligned : C_PHYS(.data.page_aligned) { *(.data.page_aligned) } + + . = ALIGN(L1_CACHE_BYTES); + __per_cpu_start = .; + .data.percpu : C_PHYS(.data.percpu) { *(.data.percpu) } + __per_cpu_end = . ; + .data.cacheline_aligned : C_PHYS(.data.cacheline_aligned) { *(.data.cacheline_aligned) } + + _edata = .; /* End of data section */ + + . = ALIGN(THREAD_SIZE); /* init_task: structure size aligned */ + .data.init_task : C_PHYS(.data.init_task) { *(.data.init_task) } + + . = ALIGN(PAGE_SIZE); /* Init code and data */ + __init_begin = .; + _sinittext = .; + .init.text : C_PHYS(.init.text) { *(.init.text) } + _einittext = .; + .init.data : C_PHYS(.init.data) { *(.init.data) } + . = ALIGN(L1_CACHE_BYTES); /* Better if Cache Line aligned */ + __setup_start = .; + .init.setup : C_PHYS(.init.setup) { *(.init.setup) } + __setup_end = .; + __initcall_start = .; + .initcall.init : C_PHYS(.initcall.init) { + *(.initcall1.init) + *(.initcall2.init) + *(.initcall3.init) + *(.initcall4.init) + *(.initcall5.init) + *(.initcall6.init) + *(.initcall7.init) + } + __initcall_end = .; + __con_initcall_start = .; + .con_initcall.init : C_PHYS(.con_initcall.init) { *(.con_initcall.init) } + __con_initcall_end = .; + SECURITY_INIT + __initramfs_start = .; + .init.ramfs : C_PHYS(.init.ramfs) { *(.init.ramfs) } + __initramfs_end = .; + . = ALIGN(PAGE_SIZE); + __init_end = .; + + /* Align to the biggest single data representation, head and tail */ + . = ALIGN(8); + __bss_start = .; /* BSS */ + .bss : C_PHYS(.bss) { + *(.bss) + } + . = ALIGN(8); + _end = . ; + + /* Sections to be discarded */ + /DISCARD/ : { + *(.exit.text) + *(.exit.data) + *(.exitcall.exit) + } + + /* Stabs debugging sections. */ + .stab 0 : C_PHYS(.stab) { *(.stab) } + .stabstr 0 : C_PHYS(.stabstr) { *(.stabstr) } + .stab.excl 0 : C_PHYS(.stab.excl) { *(.stab.excl) } + .stab.exclstr 0 : C_PHYS(.stab.exclstr) { *(.stab.exclstr) } + .stab.index 0 : C_PHYS(.stab.index) { *(.stab.index) } + .stab.indexstr 0 : C_PHYS(.stab.indexstr) { *(.stab.indexstr) } + .comment 0 : C_PHYS(.comment) { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging section are relative to the beginning + of the section so we begin .debug at 0. */ + /* DWARF 1 */ + .debug 0 : C_PHYS(.debug) { *(.debug) } + .line 0 : C_PHYS(.line) { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : C_PHYS(.debug_srcinfo) { *(.debug_srcinfo) } + .debug_sfnames 0 : C_PHYS(.debug_sfnames) { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : C_PHYS(.debug_aranges) { *(.debug_aranges) } + .debug_pubnames 0 : C_PHYS(.debug_pubnames) { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : C_PHYS(.debug_info) { *(.debug_info) } + .debug_abbrev 0 : C_PHYS(.debug_abbrev) { *(.debug_abbrev) } + .debug_line 0 : C_PHYS(.debug_line) { *(.debug_line) } + .debug_frame 0 : C_PHYS(.debug_frame) { *(.debug_frame) } + .debug_str 0 : C_PHYS(.debug_str) { *(.debug_str) } + .debug_loc 0 : C_PHYS(.debug_loc) { *(.debug_loc) } + .debug_macinfo 0 : C_PHYS(.debug_macinfo) { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : C_PHYS(.debug_weaknames) { *(.debug_weaknames) } + .debug_funcnames 0 : C_PHYS(.debug_funcnames) { *(.debug_funcnames) } + .debug_typenames 0 : C_PHYS(.debug_typenames) { *(.debug_typenames) } + .debug_varnames 0 : C_PHYS(.debug_varnames) { *(.debug_varnames) } + /* These must appear regardless of . */ +} |