diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-17 02:20:36 +0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/sh/kernel | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz |
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/sh/kernel')
45 files changed, 11373 insertions, 0 deletions
diff --git a/arch/sh/kernel/Makefile b/arch/sh/kernel/Makefile new file mode 100644 index 000000000000..8b819698df14 --- /dev/null +++ b/arch/sh/kernel/Makefile @@ -0,0 +1,22 @@ +# +# Makefile for the Linux/SuperH kernel. +# + +extra-y := head.o init_task.o vmlinux.lds + +obj-y := process.o signal.o entry.o traps.o irq.o \ + ptrace.o setup.o time.o sys_sh.o semaphore.o \ + io.o io_generic.o sh_ksyms.o + +obj-y += cpu/ + +obj-$(CONFIG_SMP) += smp.o +obj-$(CONFIG_CF_ENABLER) += cf-enabler.o +obj-$(CONFIG_SH_STANDARD_BIOS) += sh_bios.o +obj-$(CONFIG_SH_KGDB) += kgdb_stub.o kgdb_jmp.o +obj-$(CONFIG_SH_CPU_FREQ) += cpufreq.o +obj-$(CONFIG_MODULES) += module.o +obj-$(CONFIG_EARLY_PRINTK) += early_printk.o + +USE_STANDARD_AS_RULE := true + diff --git a/arch/sh/kernel/asm-offsets.c b/arch/sh/kernel/asm-offsets.c new file mode 100644 index 000000000000..dc6725c51a89 --- /dev/null +++ b/arch/sh/kernel/asm-offsets.c @@ -0,0 +1,32 @@ +/* + * 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_CPU, offsetof(struct thread_info, cpu)); + DEFINE(TI_PRE_COUNT, offsetof(struct thread_info, preempt_count)); + DEFINE(TI_RESTART_BLOCK,offsetof(struct thread_info, restart_block)); + + return 0; +} diff --git a/arch/sh/kernel/cf-enabler.c b/arch/sh/kernel/cf-enabler.c new file mode 100644 index 000000000000..7a3b18faa277 --- /dev/null +++ b/arch/sh/kernel/cf-enabler.c @@ -0,0 +1,158 @@ +/* $Id: cf-enabler.c,v 1.4 2004/02/22 22:44:36 kkojima Exp $ + * + * linux/drivers/block/cf-enabler.c + * + * Copyright (C) 1999 Niibe Yutaka + * Copyright (C) 2000 Toshiharu Nozawa + * Copyright (C) 2001 A&D Co., Ltd. + * + * Enable the CF configuration. + */ + +#include <linux/config.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/irq.h> + +/* + * You can connect Compact Flash directly to the bus of SuperH. + * This is the enabler for that. + * + * SIM: How generic is this really? It looks pretty board, or at + * least SH sub-type, specific to me. + * I know it doesn't work on the Overdrive! + */ + +/* + * 0xB8000000 : Attribute + * 0xB8001000 : Common Memory + * 0xBA000000 : I/O + */ +#if defined(CONFIG_IDE) && defined(CONFIG_CPU_SH4) +/* SH4 can't access PCMCIA interface through P2 area. + * we must remap it with appropreate attribute bit of the page set. + * this part is based on Greg Banks' hd64465_ss.c implementation - Masahiro Abe */ +#include <linux/mm.h> +#include <linux/vmalloc.h> + +#if defined(CONFIG_CF_AREA6) +#define slot_no 0 +#else +#define slot_no 1 +#endif + +/* defined in mm/ioremap.c */ +extern void * p3_ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags); + +/* use this pointer to access to directly connected compact flash io area*/ +void *cf_io_base; + +static int __init allocate_cf_area(void) +{ + pgprot_t prot; + unsigned long paddrbase, psize; + + /* open I/O area window */ + paddrbase = virt_to_phys((void*)CONFIG_CF_BASE_ADDR); + psize = PAGE_SIZE; + prot = PAGE_KERNEL_PCC(slot_no, _PAGE_PCC_IO16); + cf_io_base = p3_ioremap(paddrbase, psize, prot.pgprot); + if (!cf_io_base) { + printk("allocate_cf_area : can't open CF I/O window!\n"); + return -ENOMEM; + } +/* printk("p3_ioremap(paddr=0x%08lx, psize=0x%08lx, prot=0x%08lx)=0x%08lx\n", + paddrbase, psize, prot.pgprot, cf_io_base);*/ + + /* XXX : do we need attribute and common-memory area also? */ + + return 0; +} +#endif + +static int __init cf_init_default(void) +{ +/* You must have enabled the card, and set the level interrupt + * before reaching this point. Possibly in boot ROM or boot loader. + */ +#if defined(CONFIG_IDE) && defined(CONFIG_CPU_SH4) + allocate_cf_area(); +#endif +#if defined(CONFIG_SH_UNKNOWN) + /* This should be done in each board's init_xxx_irq. */ + make_imask_irq(14); + disable_irq(14); +#endif + return 0; +} + +#if defined(CONFIG_SH_SOLUTION_ENGINE) +#include <asm/se/se.h> + +/* + * SolutionEngine + * + * 0xB8400000 : Common Memory + * 0xB8500000 : Attribute + * 0xB8600000 : I/O + */ + +static int __init cf_init_se(void) +{ + if ((ctrl_inw(MRSHPC_CSR) & 0x000c) != 0) + return 0; /* Not detected */ + + if ((ctrl_inw(MRSHPC_CSR) & 0x0080) == 0) { + ctrl_outw(0x0674, MRSHPC_CPWCR); /* Card Vcc is 3.3v? */ + } else { + ctrl_outw(0x0678, MRSHPC_CPWCR); /* Card Vcc is 5V */ + } + + /* + * PC-Card window open + * flag == COMMON/ATTRIBUTE/IO + */ + /* common window open */ + ctrl_outw(0x8a84, MRSHPC_MW0CR1);/* window 0xb8400000 */ + if((ctrl_inw(MRSHPC_CSR) & 0x4000) != 0) + /* common mode & bus width 16bit SWAP = 1*/ + ctrl_outw(0x0b00, MRSHPC_MW0CR2); + else + /* common mode & bus width 16bit SWAP = 0*/ + ctrl_outw(0x0300, MRSHPC_MW0CR2); + + /* attribute window open */ + ctrl_outw(0x8a85, MRSHPC_MW1CR1);/* window 0xb8500000 */ + if ((ctrl_inw(MRSHPC_CSR) & 0x4000) != 0) + /* attribute mode & bus width 16bit SWAP = 1*/ + ctrl_outw(0x0a00, MRSHPC_MW1CR2); + else + /* attribute mode & bus width 16bit SWAP = 0*/ + ctrl_outw(0x0200, MRSHPC_MW1CR2); + + /* I/O window open */ + ctrl_outw(0x8a86, MRSHPC_IOWCR1);/* I/O window 0xb8600000 */ + ctrl_outw(0x0008, MRSHPC_CDCR); /* I/O card mode */ + if ((ctrl_inw(MRSHPC_CSR) & 0x4000) != 0) + ctrl_outw(0x0a00, MRSHPC_IOWCR2); /* bus width 16bit SWAP = 1*/ + else + ctrl_outw(0x0200, MRSHPC_IOWCR2); /* bus width 16bit SWAP = 0*/ + + ctrl_outw(0x2000, MRSHPC_ICR); + ctrl_outb(0x00, PA_MRSHPC_MW2 + 0x206); + ctrl_outb(0x42, PA_MRSHPC_MW2 + 0x200); + return 0; +} +#endif + +int __init cf_init(void) +{ +#if defined(CONFIG_SH_SOLUTION_ENGINE) + if (MACH_SE) + return cf_init_se(); +#endif + return cf_init_default(); +} + +__initcall (cf_init); diff --git a/arch/sh/kernel/cpu/Makefile b/arch/sh/kernel/cpu/Makefile new file mode 100644 index 000000000000..cd43714df61a --- /dev/null +++ b/arch/sh/kernel/cpu/Makefile @@ -0,0 +1,16 @@ +# +# Makefile for the Linux/SuperH CPU-specifc backends. +# + +obj-y := irq_ipr.o irq_imask.o init.o bus.o + +obj-$(CONFIG_CPU_SH2) += sh2/ +obj-$(CONFIG_CPU_SH3) += sh3/ +obj-$(CONFIG_CPU_SH4) += sh4/ + +obj-$(CONFIG_SH_RTC) += rtc.o +obj-$(CONFIG_UBC_WAKEUP) += ubc.o +obj-$(CONFIG_SH_ADC) += adc.o + +USE_STANDARD_AS_RULE := true + diff --git a/arch/sh/kernel/cpu/adc.c b/arch/sh/kernel/cpu/adc.c new file mode 100644 index 000000000000..da3d6877f93d --- /dev/null +++ b/arch/sh/kernel/cpu/adc.c @@ -0,0 +1,36 @@ +/* + * linux/arch/sh/kernel/adc.c -- SH3 on-chip ADC support + * + * Copyright (C) 2004 Andriy Skulysh <askulysh@image.kiev.ua> + */ + +#include <linux/module.h> +#include <asm/adc.h> +#include <asm/io.h> + + +int adc_single(unsigned int channel) +{ + int off; + unsigned char csr; + + if (channel >= 8) return -1; + + off = (channel & 0x03) << 2; + + csr = ctrl_inb(ADCSR); + csr = channel | ADCSR_ADST | ADCSR_CKS; + ctrl_outb(csr, ADCSR); + + do { + csr = ctrl_inb(ADCSR); + } while ((csr & ADCSR_ADF) == 0); + + csr &= ~(ADCSR_ADF | ADCSR_ADST); + ctrl_outb(csr, ADCSR); + + return (((ctrl_inb(ADDRAH + off) << 8) | + ctrl_inb(ADDRAL + off)) >> 6); +} + +EXPORT_SYMBOL(adc_single); diff --git a/arch/sh/kernel/cpu/bus.c b/arch/sh/kernel/cpu/bus.c new file mode 100644 index 000000000000..ace82f4b4a59 --- /dev/null +++ b/arch/sh/kernel/cpu/bus.c @@ -0,0 +1,195 @@ +/* + * arch/sh/kernel/cpu/bus.c + * + * Virtual bus for SuperH. + * + * Copyright (C) 2004 Paul Mundt + * + * Shamelessly cloned from arch/arm/mach-omap/bus.c, which was written + * by: + * + * Copyright (C) 2003 - 2004 Nokia Corporation + * Written by Tony Lindgren <tony@atomide.com> + * Portions of code based on sa1111.c. + * + * 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. + */ +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/module.h> +#include <asm/bus-sh.h> + +static int sh_bus_match(struct device *dev, struct device_driver *drv) +{ + struct sh_driver *shdrv = to_sh_driver(drv); + struct sh_dev *shdev = to_sh_dev(dev); + + return shdev->dev_id == shdrv->dev_id; +} + +static int sh_bus_suspend(struct device *dev, u32 state) +{ + struct sh_dev *shdev = to_sh_dev(dev); + struct sh_driver *shdrv = to_sh_driver(dev->driver); + + if (shdrv && shdrv->suspend) + return shdrv->suspend(shdev, state); + + return 0; +} + +static int sh_bus_resume(struct device *dev) +{ + struct sh_dev *shdev = to_sh_dev(dev); + struct sh_driver *shdrv = to_sh_driver(dev->driver); + + if (shdrv && shdrv->resume) + return shdrv->resume(shdev); + + return 0; +} + +static struct device sh_bus_devices[SH_NR_BUSES] = { + { + .bus_id = SH_BUS_NAME_VIRT, + }, +}; + +struct bus_type sh_bus_types[SH_NR_BUSES] = { + { + .name = SH_BUS_NAME_VIRT, + .match = sh_bus_match, + .suspend = sh_bus_suspend, + .resume = sh_bus_resume, + }, +}; + +static int sh_device_probe(struct device *dev) +{ + struct sh_dev *shdev = to_sh_dev(dev); + struct sh_driver *shdrv = to_sh_driver(dev->driver); + + if (shdrv && shdrv->probe) + return shdrv->probe(shdev); + + return -ENODEV; +} + +static int sh_device_remove(struct device *dev) +{ + struct sh_dev *shdev = to_sh_dev(dev); + struct sh_driver *shdrv = to_sh_driver(dev->driver); + + if (shdrv && shdrv->remove) + return shdrv->remove(shdev); + + return 0; +} + +int sh_device_register(struct sh_dev *dev) +{ + if (!dev) + return -EINVAL; + + if (dev->bus_id < 0 || dev->bus_id >= SH_NR_BUSES) { + printk(KERN_ERR "%s: bus_id invalid: %s bus: %d\n", + __FUNCTION__, dev->name, dev->bus_id); + return -EINVAL; + } + + dev->dev.parent = &sh_bus_devices[dev->bus_id]; + dev->dev.bus = &sh_bus_types[dev->bus_id]; + + /* This is needed for USB OHCI to work */ + if (dev->dma_mask) + dev->dev.dma_mask = dev->dma_mask; + + snprintf(dev->dev.bus_id, BUS_ID_SIZE, "%s%u", + dev->name, dev->dev_id); + + printk(KERN_INFO "Registering SH device '%s'. Parent at %s\n", + dev->dev.bus_id, dev->dev.parent->bus_id); + + return device_register(&dev->dev); +} + +void sh_device_unregister(struct sh_dev *dev) +{ + device_unregister(&dev->dev); +} + +int sh_driver_register(struct sh_driver *drv) +{ + if (!drv) + return -EINVAL; + + if (drv->bus_id < 0 || drv->bus_id >= SH_NR_BUSES) { + printk(KERN_ERR "%s: bus_id invalid: bus: %d device %d\n", + __FUNCTION__, drv->bus_id, drv->dev_id); + return -EINVAL; + } + + drv->drv.probe = sh_device_probe; + drv->drv.remove = sh_device_remove; + drv->drv.bus = &sh_bus_types[drv->bus_id]; + + return driver_register(&drv->drv); +} + +void sh_driver_unregister(struct sh_driver *drv) +{ + driver_unregister(&drv->drv); +} + +static int __init sh_bus_init(void) +{ + int i, ret = 0; + + for (i = 0; i < SH_NR_BUSES; i++) { + ret = device_register(&sh_bus_devices[i]); + if (ret != 0) { + printk(KERN_ERR "Unable to register bus device %s\n", + sh_bus_devices[i].bus_id); + continue; + } + + ret = bus_register(&sh_bus_types[i]); + if (ret != 0) { + printk(KERN_ERR "Unable to register bus %s\n", + sh_bus_types[i].name); + device_unregister(&sh_bus_devices[i]); + } + } + + printk(KERN_INFO "SH Virtual Bus initialized\n"); + + return ret; +} + +static void __exit sh_bus_exit(void) +{ + int i; + + for (i = 0; i < SH_NR_BUSES; i++) { + bus_unregister(&sh_bus_types[i]); + device_unregister(&sh_bus_devices[i]); + } +} + +module_init(sh_bus_init); +module_exit(sh_bus_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("SH Virtual Bus"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(sh_bus_types); +EXPORT_SYMBOL(sh_device_register); +EXPORT_SYMBOL(sh_device_unregister); +EXPORT_SYMBOL(sh_driver_register); +EXPORT_SYMBOL(sh_driver_unregister); + diff --git a/arch/sh/kernel/cpu/init.c b/arch/sh/kernel/cpu/init.c new file mode 100644 index 000000000000..cf94e8ef17c5 --- /dev/null +++ b/arch/sh/kernel/cpu/init.c @@ -0,0 +1,222 @@ +/* + * arch/sh/kernel/cpu/init.c + * + * CPU init code + * + * Copyright (C) 2002, 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/kernel.h> +#include <asm/processor.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/cacheflush.h> +#include <asm/cache.h> +#include <asm/io.h> + +extern void detect_cpu_and_cache_system(void); + +/* + * Generic wrapper for command line arguments to disable on-chip + * peripherals (nofpu, nodsp, and so forth). + */ +#define onchip_setup(x) \ +static int x##_disabled __initdata = 0; \ + \ +static int __init x##_setup(char *opts) \ +{ \ + x##_disabled = 1; \ + return 0; \ +} \ +__setup("no" __stringify(x), x##_setup); + +onchip_setup(fpu); +onchip_setup(dsp); + +/* + * Generic first-level cache init + */ +static void __init cache_init(void) +{ + unsigned long ccr, flags; + + if (cpu_data->type == CPU_SH_NONE) + panic("Unknown CPU"); + + jump_to_P2(); + ccr = ctrl_inl(CCR); + + /* + * If the cache is already enabled .. flush it. + */ + if (ccr & CCR_CACHE_ENABLE) { + unsigned long ways, waysize, addrstart; + + waysize = cpu_data->dcache.sets; + + /* + * If the OC is already in RAM mode, we only have + * half of the entries to flush.. + */ + if (ccr & CCR_CACHE_ORA) + waysize >>= 1; + + waysize <<= cpu_data->dcache.entry_shift; + +#ifdef CCR_CACHE_EMODE + /* If EMODE is not set, we only have 1 way to flush. */ + if (!(ccr & CCR_CACHE_EMODE)) + ways = 1; + else +#endif + ways = cpu_data->dcache.ways; + + addrstart = CACHE_OC_ADDRESS_ARRAY; + do { + unsigned long addr; + + for (addr = addrstart; + addr < addrstart + waysize; + addr += cpu_data->dcache.linesz) + ctrl_outl(0, addr); + + addrstart += cpu_data->dcache.way_incr; + } while (--ways); + } + + /* + * Default CCR values .. enable the caches + * and invalidate them immediately.. + */ + flags = CCR_CACHE_ENABLE | CCR_CACHE_INVALIDATE; + +#ifdef CCR_CACHE_EMODE + /* Force EMODE if possible */ + if (cpu_data->dcache.ways > 1) + flags |= CCR_CACHE_EMODE; +#endif + +#ifdef CONFIG_SH_WRITETHROUGH + /* Turn on Write-through caching */ + flags |= CCR_CACHE_WT; +#else + /* .. or default to Write-back */ + flags |= CCR_CACHE_CB; +#endif + +#ifdef CONFIG_SH_OCRAM + /* Turn on OCRAM -- halve the OC */ + flags |= CCR_CACHE_ORA; + cpu_data->dcache.sets >>= 1; +#endif + + ctrl_outl(flags, CCR); + back_to_P1(); +} + +#ifdef CONFIG_SH_DSP +static void __init release_dsp(void) +{ + unsigned long sr; + + /* Clear SR.DSP bit */ + __asm__ __volatile__ ( + "stc\tsr, %0\n\t" + "and\t%1, %0\n\t" + "ldc\t%0, sr\n\t" + : "=&r" (sr) + : "r" (~SR_DSP) + ); +} + +static void __init dsp_init(void) +{ + unsigned long sr; + + /* + * Set the SR.DSP bit, wait for one instruction, and then read + * back the SR value. + */ + __asm__ __volatile__ ( + "stc\tsr, %0\n\t" + "or\t%1, %0\n\t" + "ldc\t%0, sr\n\t" + "nop\n\t" + "stc\tsr, %0\n\t" + : "=&r" (sr) + : "r" (SR_DSP) + ); + + /* If the DSP bit is still set, this CPU has a DSP */ + if (sr & SR_DSP) + cpu_data->flags |= CPU_HAS_DSP; + + /* Now that we've determined the DSP status, clear the DSP bit. */ + release_dsp(); +} +#endif /* CONFIG_SH_DSP */ + +/** + * sh_cpu_init + * + * This is our initial entry point for each CPU, and is invoked on the boot + * CPU prior to calling start_kernel(). For SMP, a combination of this and + * start_secondary() will bring up each processor to a ready state prior + * to hand forking the idle loop. + * + * We do all of the basic processor init here, including setting up the + * caches, FPU, DSP, kicking the UBC, etc. By the time start_kernel() is + * hit (and subsequently platform_setup()) things like determining the + * CPU subtype and initial configuration will all be done. + * + * Each processor family is still responsible for doing its own probing + * and cache configuration in detect_cpu_and_cache_system(). + */ +asmlinkage void __init sh_cpu_init(void) +{ + /* First, probe the CPU */ + detect_cpu_and_cache_system(); + + /* Init the cache */ + cache_init(); + + /* Disable the FPU */ + if (fpu_disabled) { + printk("FPU Disabled\n"); + cpu_data->flags &= ~CPU_HAS_FPU; + disable_fpu(); + } + + /* FPU initialization */ + if ((cpu_data->flags & CPU_HAS_FPU)) { + clear_thread_flag(TIF_USEDFPU); + clear_used_math(); + } + +#ifdef CONFIG_SH_DSP + /* Probe for DSP */ + dsp_init(); + + /* Disable the DSP */ + if (dsp_disabled) { + printk("DSP Disabled\n"); + cpu_data->flags &= ~CPU_HAS_DSP; + release_dsp(); + } +#endif + +#ifdef CONFIG_UBC_WAKEUP + /* + * Some brain-damaged loaders decided it would be a good idea to put + * the UBC to sleep. This causes some issues when it comes to things + * like PTRACE_SINGLESTEP or doing hardware watchpoints in GDB. So .. + * we wake it up and hope that all is well. + */ + ubc_wakeup(); +#endif +} + diff --git a/arch/sh/kernel/cpu/irq_imask.c b/arch/sh/kernel/cpu/irq_imask.c new file mode 100644 index 000000000000..f76901e732fb --- /dev/null +++ b/arch/sh/kernel/cpu/irq_imask.c @@ -0,0 +1,116 @@ +/* $Id: irq_imask.c,v 1.1.2.1 2002/11/17 10:53:43 mrbrown Exp $ + * + * linux/arch/sh/kernel/irq_imask.c + * + * Copyright (C) 1999, 2000 Niibe Yutaka + * + * Simple interrupt handling using IMASK of SR register. + * + */ + +/* NOTE: Will not work on level 15 */ + + +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/irq.h> + +#include <linux/spinlock.h> +#include <linux/cache.h> +#include <linux/irq.h> + +/* Bitmap of IRQ masked */ +static unsigned long imask_mask = 0x7fff; +static int interrupt_priority = 0; + +static void enable_imask_irq(unsigned int irq); +static void disable_imask_irq(unsigned int irq); +static void shutdown_imask_irq(unsigned int irq); +static void mask_and_ack_imask(unsigned int); +static void end_imask_irq(unsigned int irq); + +#define IMASK_PRIORITY 15 + +static unsigned int startup_imask_irq(unsigned int irq) +{ + /* Nothing to do */ + return 0; /* never anything pending */ +} + +static struct hw_interrupt_type imask_irq_type = { + "SR.IMASK", + startup_imask_irq, + shutdown_imask_irq, + enable_imask_irq, + disable_imask_irq, + mask_and_ack_imask, + end_imask_irq +}; + +void static inline set_interrupt_registers(int ip) +{ + unsigned long __dummy; + + asm volatile("ldc %2, r6_bank\n\t" + "stc sr, %0\n\t" + "and #0xf0, %0\n\t" + "shlr2 %0\n\t" + "cmp/eq #0x3c, %0\n\t" + "bt/s 1f ! CLI-ed\n\t" + " stc sr, %0\n\t" + "and %1, %0\n\t" + "or %2, %0\n\t" + "ldc %0, sr\n" + "1:" + : "=&z" (__dummy) + : "r" (~0xf0), "r" (ip << 4) + : "t"); +} + +static void disable_imask_irq(unsigned int irq) +{ + clear_bit(irq, &imask_mask); + if (interrupt_priority < IMASK_PRIORITY - irq) + interrupt_priority = IMASK_PRIORITY - irq; + + set_interrupt_registers(interrupt_priority); +} + +static void enable_imask_irq(unsigned int irq) +{ + set_bit(irq, &imask_mask); + interrupt_priority = IMASK_PRIORITY - ffz(imask_mask); + + set_interrupt_registers(interrupt_priority); +} + +static void mask_and_ack_imask(unsigned int irq) +{ + disable_imask_irq(irq); +} + +static void end_imask_irq(unsigned int irq) +{ + if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) + enable_imask_irq(irq); +} + +static void shutdown_imask_irq(unsigned int irq) +{ + /* Nothing to do */ +} + +void make_imask_irq(unsigned int irq) +{ + disable_irq_nosync(irq); + irq_desc[irq].handler = &imask_irq_type; + enable_irq(irq); +} diff --git a/arch/sh/kernel/cpu/irq_ipr.c b/arch/sh/kernel/cpu/irq_ipr.c new file mode 100644 index 000000000000..7ea3d2d030e5 --- /dev/null +++ b/arch/sh/kernel/cpu/irq_ipr.c @@ -0,0 +1,339 @@ +/* $Id: irq_ipr.c,v 1.1.2.1 2002/11/17 10:53:43 mrbrown Exp $ + * + * linux/arch/sh/kernel/irq_ipr.c + * + * Copyright (C) 1999 Niibe Yutaka & Takeshi Yaegashi + * Copyright (C) 2000 Kazumoto Kojima + * Copyright (C) 2003 Takashi Kusuda <kusuda-takashi@hitachi-ul.co.jp> + * + * Interrupt handling for IPR-based IRQ. + * + * Supported system: + * On-chip supporting modules (TMU, RTC, etc.). + * On-chip supporting modules for SH7709/SH7709A/SH7729/SH7300. + * Hitachi SolutionEngine external I/O: + * MS7709SE01, MS7709ASE01, and MS7750SE01 + * + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/module.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/machvec.h> + +struct ipr_data { + unsigned int addr; /* Address of Interrupt Priority Register */ + int shift; /* Shifts of the 16-bit data */ + int priority; /* The priority */ +}; +static struct ipr_data ipr_data[NR_IRQS]; + +static void enable_ipr_irq(unsigned int irq); +static void disable_ipr_irq(unsigned int irq); + +/* shutdown is same as "disable" */ +#define shutdown_ipr_irq disable_ipr_irq + +static void mask_and_ack_ipr(unsigned int); +static void end_ipr_irq(unsigned int irq); + +static unsigned int startup_ipr_irq(unsigned int irq) +{ + enable_ipr_irq(irq); + return 0; /* never anything pending */ +} + +static struct hw_interrupt_type ipr_irq_type = { + "IPR-IRQ", + startup_ipr_irq, + shutdown_ipr_irq, + enable_ipr_irq, + disable_ipr_irq, + mask_and_ack_ipr, + end_ipr_irq +}; + +static void disable_ipr_irq(unsigned int irq) +{ + unsigned long val, flags; + unsigned int addr = ipr_data[irq].addr; + unsigned short mask = 0xffff ^ (0x0f << ipr_data[irq].shift); + + /* Set the priority in IPR to 0 */ + local_irq_save(flags); + val = ctrl_inw(addr); + val &= mask; + ctrl_outw(val, addr); + local_irq_restore(flags); +} + +static void enable_ipr_irq(unsigned int irq) +{ + unsigned long val, flags; + unsigned int addr = ipr_data[irq].addr; + int priority = ipr_data[irq].priority; + unsigned short value = (priority << ipr_data[irq].shift); + + /* Set priority in IPR back to original value */ + local_irq_save(flags); + val = ctrl_inw(addr); + val |= value; + ctrl_outw(val, addr); + local_irq_restore(flags); +} + +static void mask_and_ack_ipr(unsigned int irq) +{ + disable_ipr_irq(irq); + +#if defined(CONFIG_CPU_SUBTYPE_SH7707) || defined(CONFIG_CPU_SUBTYPE_SH7709) || \ + defined(CONFIG_CPU_SUBTYPE_SH7300) || defined(CONFIG_CPU_SUBTYPE_SH7705) + /* This is needed when we use edge triggered setting */ + /* XXX: Is it really needed? */ + if (IRQ0_IRQ <= irq && irq <= IRQ5_IRQ) { + /* Clear external interrupt request */ + int a = ctrl_inb(INTC_IRR0); + a &= ~(1 << (irq - IRQ0_IRQ)); + ctrl_outb(a, INTC_IRR0); + } +#endif +} + +static void end_ipr_irq(unsigned int irq) +{ + if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) + enable_ipr_irq(irq); +} + +void make_ipr_irq(unsigned int irq, unsigned int addr, int pos, int priority) +{ + disable_irq_nosync(irq); + ipr_data[irq].addr = addr; + ipr_data[irq].shift = pos*4; /* POSition (0-3) x 4 means shift */ + ipr_data[irq].priority = priority; + + irq_desc[irq].handler = &ipr_irq_type; + disable_ipr_irq(irq); +} + +#if defined(CONFIG_CPU_SUBTYPE_SH7705) || \ + defined(CONFIG_CPU_SUBTYPE_SH7707) || \ + defined(CONFIG_CPU_SUBTYPE_SH7709) +static unsigned char pint_map[256]; +static unsigned long portcr_mask = 0; + +static void enable_pint_irq(unsigned int irq); +static void disable_pint_irq(unsigned int irq); + +/* shutdown is same as "disable" */ +#define shutdown_pint_irq disable_pint_irq + +static void mask_and_ack_pint(unsigned int); +static void end_pint_irq(unsigned int irq); + +static unsigned int startup_pint_irq(unsigned int irq) +{ + enable_pint_irq(irq); + return 0; /* never anything pending */ +} + +static struct hw_interrupt_type pint_irq_type = { + "PINT-IRQ", + startup_pint_irq, + shutdown_pint_irq, + enable_pint_irq, + disable_pint_irq, + mask_and_ack_pint, + end_pint_irq +}; + +static void disable_pint_irq(unsigned int irq) +{ + unsigned long val, flags; + + local_irq_save(flags); + val = ctrl_inw(INTC_INTER); + val &= ~(1 << (irq - PINT_IRQ_BASE)); + ctrl_outw(val, INTC_INTER); /* disable PINTn */ + portcr_mask &= ~(3 << (irq - PINT_IRQ_BASE)*2); + local_irq_restore(flags); +} + +static void enable_pint_irq(unsigned int irq) +{ + unsigned long val, flags; + + local_irq_save(flags); + val = ctrl_inw(INTC_INTER); + val |= 1 << (irq - PINT_IRQ_BASE); + ctrl_outw(val, INTC_INTER); /* enable PINTn */ + portcr_mask |= 3 << (irq - PINT_IRQ_BASE)*2; + local_irq_restore(flags); +} + +static void mask_and_ack_pint(unsigned int irq) +{ + disable_pint_irq(irq); +} + +static void end_pint_irq(unsigned int irq) +{ + if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) + enable_pint_irq(irq); +} + +void make_pint_irq(unsigned int irq) +{ + disable_irq_nosync(irq); + irq_desc[irq].handler = &pint_irq_type; + disable_pint_irq(irq); +} +#endif + +void __init init_IRQ(void) +{ +#if defined(CONFIG_CPU_SUBTYPE_SH7705) || \ + defined(CONFIG_CPU_SUBTYPE_SH7707) || \ + defined(CONFIG_CPU_SUBTYPE_SH7709) + int i; +#endif + + make_ipr_irq(TIMER_IRQ, TIMER_IPR_ADDR, TIMER_IPR_POS, TIMER_PRIORITY); + make_ipr_irq(TIMER1_IRQ, TIMER1_IPR_ADDR, TIMER1_IPR_POS, TIMER1_PRIORITY); +#if defined(CONFIG_SH_RTC) + make_ipr_irq(RTC_IRQ, RTC_IPR_ADDR, RTC_IPR_POS, RTC_PRIORITY); +#endif + +#ifdef SCI_ERI_IRQ + make_ipr_irq(SCI_ERI_IRQ, SCI_IPR_ADDR, SCI_IPR_POS, SCI_PRIORITY); + make_ipr_irq(SCI_RXI_IRQ, SCI_IPR_ADDR, SCI_IPR_POS, SCI_PRIORITY); + make_ipr_irq(SCI_TXI_IRQ, SCI_IPR_ADDR, SCI_IPR_POS, SCI_PRIORITY); +#endif + +#ifdef SCIF1_ERI_IRQ + make_ipr_irq(SCIF1_ERI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY); + make_ipr_irq(SCIF1_RXI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY); + make_ipr_irq(SCIF1_BRI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY); + make_ipr_irq(SCIF1_TXI_IRQ, SCIF1_IPR_ADDR, SCIF1_IPR_POS, SCIF1_PRIORITY); +#endif + +#if defined(CONFIG_CPU_SUBTYPE_SH7300) + make_ipr_irq(SCIF0_IRQ, SCIF0_IPR_ADDR, SCIF0_IPR_POS, SCIF0_PRIORITY); + make_ipr_irq(DMTE2_IRQ, DMA1_IPR_ADDR, DMA1_IPR_POS, DMA1_PRIORITY); + make_ipr_irq(DMTE3_IRQ, DMA1_IPR_ADDR, DMA1_IPR_POS, DMA1_PRIORITY); + make_ipr_irq(VIO_IRQ, VIO_IPR_ADDR, VIO_IPR_POS, VIO_PRIORITY); +#endif + +#ifdef SCIF_ERI_IRQ + make_ipr_irq(SCIF_ERI_IRQ, SCIF_IPR_ADDR, SCIF_IPR_POS, SCIF_PRIORITY); + make_ipr_irq(SCIF_RXI_IRQ, SCIF_IPR_ADDR, SCIF_IPR_POS, SCIF_PRIORITY); + make_ipr_irq(SCIF_BRI_IRQ, SCIF_IPR_ADDR, SCIF_IPR_POS, SCIF_PRIORITY); + make_ipr_irq(SCIF_TXI_IRQ, SCIF_IPR_ADDR, SCIF_IPR_POS, SCIF_PRIORITY); +#endif + +#ifdef IRDA_ERI_IRQ + make_ipr_irq(IRDA_ERI_IRQ, IRDA_IPR_ADDR, IRDA_IPR_POS, IRDA_PRIORITY); + make_ipr_irq(IRDA_RXI_IRQ, IRDA_IPR_ADDR, IRDA_IPR_POS, IRDA_PRIORITY); + make_ipr_irq(IRDA_BRI_IRQ, IRDA_IPR_ADDR, IRDA_IPR_POS, IRDA_PRIORITY); + make_ipr_irq(IRDA_TXI_IRQ, IRDA_IPR_ADDR, IRDA_IPR_POS, IRDA_PRIORITY); +#endif + +#if defined(CONFIG_CPU_SUBTYPE_SH7707) || defined(CONFIG_CPU_SUBTYPE_SH7709) || \ + defined(CONFIG_CPU_SUBTYPE_SH7300) || defined(CONFIG_CPU_SUBTYPE_SH7705) + /* + * Initialize the Interrupt Controller (INTC) + * registers to their power on values + */ + + /* + * Enable external irq (INTC IRQ mode). + * You should set corresponding bits of PFC to "00" + * to enable these interrupts. + */ + make_ipr_irq(IRQ0_IRQ, IRQ0_IPR_ADDR, IRQ0_IPR_POS, IRQ0_PRIORITY); + make_ipr_irq(IRQ1_IRQ, IRQ1_IPR_ADDR, IRQ1_IPR_POS, IRQ1_PRIORITY); + make_ipr_irq(IRQ2_IRQ, IRQ2_IPR_ADDR, IRQ2_IPR_POS, IRQ2_PRIORITY); + make_ipr_irq(IRQ3_IRQ, IRQ3_IPR_ADDR, IRQ3_IPR_POS, IRQ3_PRIORITY); + make_ipr_irq(IRQ4_IRQ, IRQ4_IPR_ADDR, IRQ4_IPR_POS, IRQ4_PRIORITY); + make_ipr_irq(IRQ5_IRQ, IRQ5_IPR_ADDR, IRQ5_IPR_POS, IRQ5_PRIORITY); +#if !defined(CONFIG_CPU_SUBTYPE_SH7300) + make_ipr_irq(PINT0_IRQ, PINT0_IPR_ADDR, PINT0_IPR_POS, PINT0_PRIORITY); + make_ipr_irq(PINT8_IRQ, PINT8_IPR_ADDR, PINT8_IPR_POS, PINT8_PRIORITY); + enable_ipr_irq(PINT0_IRQ); + enable_ipr_irq(PINT8_IRQ); + + for(i = 0; i < 16; i++) + make_pint_irq(PINT_IRQ_BASE + i); + for(i = 0; i < 256; i++) + { + if(i & 1) pint_map[i] = 0; + else if(i & 2) pint_map[i] = 1; + else if(i & 4) pint_map[i] = 2; + else if(i & 8) pint_map[i] = 3; + else if(i & 0x10) pint_map[i] = 4; + else if(i & 0x20) pint_map[i] = 5; + else if(i & 0x40) pint_map[i] = 6; + else if(i & 0x80) pint_map[i] = 7; + } +#endif /* !CONFIG_CPU_SUBTYPE_SH7300 */ +#endif /* CONFIG_CPU_SUBTYPE_SH7707 || CONFIG_CPU_SUBTYPE_SH7709 || CONFIG_CPU_SUBTYPE_SH7300*/ + +#ifdef CONFIG_CPU_SUBTYPE_ST40 + init_IRQ_intc2(); +#endif + + /* Perform the machine specific initialisation */ + if (sh_mv.mv_init_irq != NULL) { + sh_mv.mv_init_irq(); + } +} +#if defined(CONFIG_CPU_SUBTYPE_SH7707) || defined(CONFIG_CPU_SUBTYPE_SH7709) || \ + defined(CONFIG_CPU_SUBTYPE_SH7300) || defined(CONFIG_CPU_SUBTYPE_SH7705) +int ipr_irq_demux(int irq) +{ +#if !defined(CONFIG_CPU_SUBTYPE_SH7300) + unsigned long creg, dreg, d, sav; + + if(irq == PINT0_IRQ) + { +#if defined(CONFIG_CPU_SUBTYPE_SH7707) + creg = PORT_PACR; + dreg = PORT_PADR; +#else + creg = PORT_PCCR; + dreg = PORT_PCDR; +#endif + sav = ctrl_inw(creg); + ctrl_outw(sav | portcr_mask, creg); + d = (~ctrl_inb(dreg) ^ ctrl_inw(INTC_ICR2)) & ctrl_inw(INTC_INTER) & 0xff; + ctrl_outw(sav, creg); + if(d == 0) return irq; + return PINT_IRQ_BASE + pint_map[d]; + } + else if(irq == PINT8_IRQ) + { +#if defined(CONFIG_CPU_SUBTYPE_SH7707) + creg = PORT_PBCR; + dreg = PORT_PBDR; +#else + creg = PORT_PFCR; + dreg = PORT_PFDR; +#endif + sav = ctrl_inw(creg); + ctrl_outw(sav | (portcr_mask >> 16), creg); + d = (~ctrl_inb(dreg) ^ (ctrl_inw(INTC_ICR2) >> 8)) & (ctrl_inw(INTC_INTER) >> 8) & 0xff; + ctrl_outw(sav, creg); + if(d == 0) return irq; + return PINT_IRQ_BASE + 8 + pint_map[d]; + } +#endif + return irq; +} +#endif + +EXPORT_SYMBOL(make_ipr_irq); + diff --git a/arch/sh/kernel/cpu/rtc.c b/arch/sh/kernel/cpu/rtc.c new file mode 100644 index 000000000000..f8361f5e788b --- /dev/null +++ b/arch/sh/kernel/cpu/rtc.c @@ -0,0 +1,136 @@ +/* + * linux/arch/sh/kernel/rtc.c -- SH3 / SH4 on-chip RTC support + * + * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org> + * Copyright (C) 1999 Tetsuya Okada & Niibe Yutaka + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/time.h> + +#include <asm/io.h> +#include <asm/rtc.h> + +#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 + +void sh_rtc_gettimeofday(struct timespec *ts) +{ + unsigned int sec128, sec, sec2, min, hr, wk, day, mon, yr, yr100, cf_bit; + unsigned long flags; + + again: + do { + local_irq_save(flags); + ctrl_outb(0, RCR1); /* Clear CF-bit */ + sec128 = ctrl_inb(R64CNT); + sec = ctrl_inb(RSECCNT); + min = ctrl_inb(RMINCNT); + hr = ctrl_inb(RHRCNT); + wk = ctrl_inb(RWKCNT); + day = ctrl_inb(RDAYCNT); + mon = ctrl_inb(RMONCNT); +#if defined(CONFIG_CPU_SH4) + yr = ctrl_inw(RYRCNT); + yr100 = (yr >> 8); + yr &= 0xff; +#else + yr = ctrl_inb(RYRCNT); + yr100 = (yr == 0x99) ? 0x19 : 0x20; +#endif + sec2 = ctrl_inb(R64CNT); + cf_bit = ctrl_inb(RCR1) & RCR1_CF; + local_irq_restore(flags); + } while (cf_bit != 0 || ((sec128 ^ sec2) & RTC_BIT_INVERTED) != 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"); + local_irq_save(flags); + 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); +#if defined(CONFIG_CPU_SH4) + ctrl_outw(0x2000, RYRCNT); +#else + ctrl_outb(0, RYRCNT); +#endif + ctrl_outb(RCR2_RTCEN|RCR2_START, RCR2); /* Start */ + goto again; + } + +#if RTC_BIT_INVERTED != 0 + if ((sec128 & RTC_BIT_INVERTED)) + sec--; +#endif + + ts->tv_sec = mktime(yr100 * 100 + yr, mon, day, hr, min, sec); + ts->tv_nsec = ((sec128 * 1000000) / 128) * 1000; +} + +/* + * Changed to only care about tv_sec, and not the full timespec struct + * (i.e. tv_nsec). It can easily be switched to timespec for future cpus + * that support setting usec or nsec RTC values. + */ +int sh_rtc_settimeofday(const time_t secs) +{ + int retval = 0; + int real_seconds, real_minutes, cmos_minutes; + unsigned long flags; + + local_irq_save(flags); + 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 = secs % 60; + real_minutes = secs / 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 */ + local_irq_restore(flags); + + return retval; +} diff --git a/arch/sh/kernel/cpu/sh2/Makefile b/arch/sh/kernel/cpu/sh2/Makefile new file mode 100644 index 000000000000..389353fba608 --- /dev/null +++ b/arch/sh/kernel/cpu/sh2/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the Linux/SuperH SH-2 backends. +# + +obj-y := probe.o + diff --git a/arch/sh/kernel/cpu/sh2/probe.c b/arch/sh/kernel/cpu/sh2/probe.c new file mode 100644 index 000000000000..f17a2a0d588e --- /dev/null +++ b/arch/sh/kernel/cpu/sh2/probe.c @@ -0,0 +1,39 @@ +/* + * arch/sh/kernel/cpu/sh2/probe.c + * + * CPU Subtype Probing for SH-2. + * + * Copyright (C) 2002 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 <asm/processor.h> +#include <asm/cache.h> + +int __init detect_cpu_and_cache_system(void) +{ + /* + * For now, assume SH7604 .. fix this later. + */ + cpu_data->type = CPU_SH7604; + cpu_data->dcache.ways = 4; + cpu_data->dcache.way_shift = 6; + cpu_data->dcache.sets = 64; + cpu_data->dcache.entry_shift = 4; + cpu_data->dcache.linesz = L1_CACHE_BYTES; + cpu_data->dcache.flags = 0; + + /* + * SH-2 doesn't have separate caches + */ + cpu_data->dcache.flags |= SH_CACHE_COMBINED; + cpu_data->icache = cpu_data->dcache; + + return 0; +} + diff --git a/arch/sh/kernel/cpu/sh3/Makefile b/arch/sh/kernel/cpu/sh3/Makefile new file mode 100644 index 000000000000..a64532e4dc63 --- /dev/null +++ b/arch/sh/kernel/cpu/sh3/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the Linux/SuperH SH-3 backends. +# + +obj-y := ex.o probe.o + diff --git a/arch/sh/kernel/cpu/sh3/ex.S b/arch/sh/kernel/cpu/sh3/ex.S new file mode 100644 index 000000000000..966c0858b714 --- /dev/null +++ b/arch/sh/kernel/cpu/sh3/ex.S @@ -0,0 +1,199 @@ +/* + * arch/sh/kernel/cpu/sh3/ex.S + * + * The SH-3 exception vector table. + + * Copyright (C) 1999, 2000, 2002 Niibe Yutaka + * 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/linkage.h> +#include <linux/config.h> + + .align 2 + .data + +ENTRY(exception_handling_table) + .long exception_error /* 000 */ + .long exception_error +#if defined(CONFIG_MMU) + .long tlb_miss_load /* 040 */ + .long tlb_miss_store + .long initial_page_write + .long tlb_protection_violation_load + .long tlb_protection_violation_store + .long address_error_load + .long address_error_store /* 100 */ +#else + .long exception_error ! tlb miss load /* 040 */ + .long exception_error ! tlb miss store + .long exception_error ! initial page write + .long exception_error ! tlb prot violation load + .long exception_error ! tlb prot violation store + .long exception_error ! address error load + .long exception_error ! address error store /* 100 */ +#endif + .long exception_error ! fpu_exception /* 120 */ + .long exception_error /* 140 */ + .long system_call ! Unconditional Trap /* 160 */ + .long exception_error ! reserved_instruction (filled by trap_init) /* 180 */ + .long exception_error ! illegal_slot_instruction (filled by trap_init) /*1A0*/ +ENTRY(nmi_slot) +#if defined (CONFIG_KGDB_NMI) + .long debug_enter /* 1C0 */ ! Allow trap to debugger +#else + .long exception_none /* 1C0 */ ! Not implemented yet +#endif +ENTRY(user_break_point_trap) + .long break_point_trap /* 1E0 */ +ENTRY(interrupt_table) + ! external hardware + .long do_IRQ ! 0000 /* 200 */ + .long do_IRQ ! 0001 + .long do_IRQ ! 0010 + .long do_IRQ ! 0011 + .long do_IRQ ! 0100 + .long do_IRQ ! 0101 + .long do_IRQ ! 0110 + .long do_IRQ ! 0111 + .long do_IRQ ! 1000 /* 300 */ + .long do_IRQ ! 1001 + .long do_IRQ ! 1010 + .long do_IRQ ! 1011 + .long do_IRQ ! 1100 + .long do_IRQ ! 1101 + .long do_IRQ ! 1110 + .long exception_error + ! Internal hardware + .long do_IRQ ! TMU0 tuni0 /* 400 */ + .long do_IRQ ! TMU1 tuni1 + .long do_IRQ ! TMU2 tuni2 + .long do_IRQ ! ticpi2 + .long do_IRQ ! RTC ati + .long do_IRQ ! pri + .long do_IRQ ! cui + .long do_IRQ ! SCI eri + .long do_IRQ ! rxi /* 500 */ + .long do_IRQ ! txi + .long do_IRQ ! tei + .long do_IRQ ! WDT iti /* 560 */ + .long do_IRQ ! REF rcmi + .long do_IRQ ! rovi + .long do_IRQ + .long do_IRQ /* 5E0 */ +#if defined(CONFIG_CPU_SUBTYPE_SH7707) || defined(CONFIG_CPU_SUBTYPE_SH7709) || \ + defined(CONFIG_CPU_SUBTYPE_SH7300) || defined(CONFIG_CPU_SUBTYPE_SH7705) + .long do_IRQ ! 32 IRQ irq0 /* 600 */ + .long do_IRQ ! 33 irq1 + .long do_IRQ ! 34 irq2 + .long do_IRQ ! 35 irq3 + .long do_IRQ ! 36 irq4 + .long do_IRQ ! 37 irq5 + .long do_IRQ ! 38 + .long do_IRQ ! 39 + .long do_IRQ ! 40 PINT pint0-7 /* 700 */ + .long do_IRQ ! 41 pint8-15 + .long do_IRQ ! 42 + .long do_IRQ ! 43 + .long do_IRQ ! 44 + .long do_IRQ ! 45 + .long do_IRQ ! 46 + .long do_IRQ ! 47 + .long do_IRQ ! 48 DMAC dei0 /* 800 */ + .long do_IRQ ! 49 dei1 + .long do_IRQ ! 50 dei2 + .long do_IRQ ! 51 dei3 + .long do_IRQ ! 52 IrDA eri1 + .long do_IRQ ! 53 rxi1 + .long do_IRQ ! 54 bri1 + .long do_IRQ ! 55 txi1 + .long do_IRQ ! 56 SCIF eri2 + .long do_IRQ ! 57 rxi2 + .long do_IRQ ! 58 bri2 + .long do_IRQ ! 59 txi2 + .long do_IRQ ! 60 ADC adi /* 980 */ +#if defined(CONFIG_CPU_SUBTYPE_SH7705) + .long exception_none ! 61 /* 9A0 */ + .long exception_none ! 62 + .long exception_none ! 63 + .long exception_none ! 64 /* A00 */ + .long do_IRQ ! 65 USB usi0 + .long do_IRQ ! 66 usi1 + .long exception_none ! 67 + .long exception_none ! 68 + .long exception_none ! 69 + .long exception_none ! 70 + .long exception_none ! 71 + .long exception_none ! 72 /* B00 */ + .long exception_none ! 73 + .long exception_none ! 74 + .long exception_none ! 75 + .long exception_none ! 76 + .long exception_none ! 77 + .long exception_none ! 78 + .long exception_none ! 79 + .long do_IRQ ! 80 TPU0 tpi0 /* C00 */ + .long do_IRQ ! 81 TPU1 tpi1 + .long exception_none ! 82 + .long exception_none ! 83 + .long do_IRQ ! 84 TPU2 tpi2 + .long do_IRQ ! 85 TPU3 tpi3 /* CA0 */ +#endif +#if defined(CONFIG_CPU_SUBTYPE_SH7707) || defined(CONFIG_CPU_SUBTYPE_SH7300) + .long do_IRQ ! 61 LCDC lcdi /* 9A0 */ + .long do_IRQ ! 62 PCC pcc0i + .long do_IRQ ! 63 pcc1i /* 9E0 */ +#endif +#if defined(CONFIG_CPU_SUBTYPE_SH7300) + .long do_IRQ ! 64 + .long do_IRQ ! 65 + .long do_IRQ ! 66 + .long do_IRQ ! 67 + .long do_IRQ ! 68 + .long do_IRQ ! 69 + .long do_IRQ ! 70 + .long do_IRQ ! 71 + .long do_IRQ ! 72 + .long do_IRQ ! 73 + .long do_IRQ ! 74 + .long do_IRQ ! 75 + .long do_IRQ ! 76 + .long do_IRQ ! 77 + .long do_IRQ ! 78 + .long do_IRQ ! 79 + .long do_IRQ ! 80 SCIF0(SH7300) + .long do_IRQ ! 81 + .long do_IRQ ! 82 + .long do_IRQ ! 83 + .long do_IRQ ! 84 + .long do_IRQ ! 85 + .long do_IRQ ! 86 + .long do_IRQ ! 87 + .long do_IRQ ! 88 + .long do_IRQ ! 89 + .long do_IRQ ! 90 + .long do_IRQ ! 91 + .long do_IRQ ! 92 + .long do_IRQ ! 93 + .long do_IRQ ! 94 + .long do_IRQ ! 95 + .long do_IRQ ! 96 + .long do_IRQ ! 97 + .long do_IRQ ! 98 + .long do_IRQ ! 99 + .long do_IRQ ! 100 + .long do_IRQ ! 101 + .long do_IRQ ! 102 + .long do_IRQ ! 103 + .long do_IRQ ! 104 + .long do_IRQ ! 105 + .long do_IRQ ! 106 + .long do_IRQ ! 107 + .long do_IRQ ! 108 +#endif +#endif + diff --git a/arch/sh/kernel/cpu/sh3/probe.c b/arch/sh/kernel/cpu/sh3/probe.c new file mode 100644 index 000000000000..5cdc88638601 --- /dev/null +++ b/arch/sh/kernel/cpu/sh3/probe.c @@ -0,0 +1,97 @@ +/* + * arch/sh/kernel/cpu/sh3/probe.c + * + * CPU Subtype Probing for SH-3. + * + * Copyright (C) 1999, 2000 Niibe Yutaka + * Copyright (C) 2002 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 <asm/processor.h> +#include <asm/cache.h> +#include <asm/io.h> + +int __init detect_cpu_and_cache_system(void) +{ + unsigned long addr0, addr1, data0, data1, data2, data3; + + jump_to_P2(); + /* + * Check if the entry shadows or not. + * When shadowed, it's 128-entry system. + * Otherwise, it's 256-entry system. + */ + addr0 = CACHE_OC_ADDRESS_ARRAY + (3 << 12); + addr1 = CACHE_OC_ADDRESS_ARRAY + (1 << 12); + + /* First, write back & invalidate */ + data0 = ctrl_inl(addr0); + ctrl_outl(data0&~(SH_CACHE_VALID|SH_CACHE_UPDATED), addr0); + data1 = ctrl_inl(addr1); + ctrl_outl(data1&~(SH_CACHE_VALID|SH_CACHE_UPDATED), addr1); + + /* Next, check if there's shadow or not */ + data0 = ctrl_inl(addr0); + data0 ^= SH_CACHE_VALID; + ctrl_outl(data0, addr0); + data1 = ctrl_inl(addr1); + data2 = data1 ^ SH_CACHE_VALID; + ctrl_outl(data2, addr1); + data3 = ctrl_inl(addr0); + + /* Lastly, invaliate them. */ + ctrl_outl(data0&~SH_CACHE_VALID, addr0); + ctrl_outl(data2&~SH_CACHE_VALID, addr1); + + back_to_P1(); + + cpu_data->dcache.ways = 4; + cpu_data->dcache.entry_shift = 4; + cpu_data->dcache.linesz = L1_CACHE_BYTES; + cpu_data->dcache.flags = 0; + + /* + * 7709A/7729 has 16K cache (256-entry), while 7702 has only + * 2K(direct) 7702 is not supported (yet) + */ + if (data0 == data1 && data2 == data3) { /* Shadow */ + cpu_data->dcache.way_incr = (1 << 11); + cpu_data->dcache.entry_mask = 0x7f0; + cpu_data->dcache.sets = 128; + cpu_data->type = CPU_SH7708; + + cpu_data->flags |= CPU_HAS_MMU_PAGE_ASSOC; + } else { /* 7709A or 7729 */ + cpu_data->dcache.way_incr = (1 << 12); + cpu_data->dcache.entry_mask = 0xff0; + cpu_data->dcache.sets = 256; + cpu_data->type = CPU_SH7729; + +#if defined(CONFIG_CPU_SUBTYPE_SH7705) + cpu_data->type = CPU_SH7705; + +#if defined(CONFIG_SH7705_CACHE_32KB) + cpu_data->dcache.way_incr = (1 << 13); + cpu_data->dcache.entry_mask = 0x1ff0; + cpu_data->dcache.sets = 512; + ctrl_outl(CCR_CACHE_32KB, CCR3); +#else + ctrl_outl(CCR_CACHE_16KB, CCR3); +#endif +#endif + } + + /* + * SH-3 doesn't have separate caches + */ + cpu_data->dcache.flags |= SH_CACHE_COMBINED; + cpu_data->icache = cpu_data->dcache; + + return 0; +} + diff --git a/arch/sh/kernel/cpu/sh4/Makefile b/arch/sh/kernel/cpu/sh4/Makefile new file mode 100644 index 000000000000..ead1071eac73 --- /dev/null +++ b/arch/sh/kernel/cpu/sh4/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the Linux/SuperH SH-4 backends. +# + +obj-y := ex.o probe.o + +obj-$(CONFIG_SH_FPU) += fpu.o +obj-$(CONFIG_CPU_SUBTYPE_ST40STB1) += irq_intc2.o +obj-$(CONFIG_SH_STORE_QUEUES) += sq.o + diff --git a/arch/sh/kernel/cpu/sh4/ex.S b/arch/sh/kernel/cpu/sh4/ex.S new file mode 100644 index 000000000000..8221e9d15515 --- /dev/null +++ b/arch/sh/kernel/cpu/sh4/ex.S @@ -0,0 +1,384 @@ +/* + * arch/sh/kernel/cpu/sh4/ex.S + * + * The SH-4 exception vector table. + + * Copyright (C) 1999, 2000, 2002 Niibe Yutaka + * 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/linkage.h> +#include <linux/config.h> + + .align 2 + .data + +ENTRY(exception_handling_table) + .long exception_error /* 000 */ + .long exception_error +#if defined(CONFIG_MMU) + .long tlb_miss_load /* 040 */ + .long tlb_miss_store + .long initial_page_write + .long tlb_protection_violation_load + .long tlb_protection_violation_store + .long address_error_load + .long address_error_store /* 100 */ +#else + .long exception_error ! tlb miss load /* 040 */ + .long exception_error ! tlb miss store + .long exception_error ! initial page write + .long exception_error ! tlb prot violation load + .long exception_error ! tlb prot violation store + .long exception_error ! address error load + .long exception_error ! address error store /* 100 */ +#endif +#if defined(CONFIG_SH_FPU) + .long do_fpu_error /* 120 */ +#else + .long exception_error /* 120 */ +#endif + .long exception_error /* 140 */ + .long system_call ! Unconditional Trap /* 160 */ + .long exception_error ! reserved_instruction (filled by trap_init) /* 180 */ + .long exception_error ! illegal_slot_instruction (filled by trap_init) /*1A0*/ +ENTRY(nmi_slot) +#if defined (CONFIG_KGDB_NMI) + .long debug_enter /* 1C0 */ ! Allow trap to debugger +#else + .long exception_none /* 1C0 */ ! Not implemented yet +#endif +ENTRY(user_break_point_trap) + .long break_point_trap /* 1E0 */ +ENTRY(interrupt_table) + ! external hardware + .long do_IRQ ! 0000 /* 200 */ + .long do_IRQ ! 0001 + .long do_IRQ ! 0010 + .long do_IRQ ! 0011 + .long do_IRQ ! 0100 + .long do_IRQ ! 0101 + .long do_IRQ ! 0110 + .long do_IRQ ! 0111 + .long do_IRQ ! 1000 /* 300 */ + .long do_IRQ ! 1001 + .long do_IRQ ! 1010 + .long do_IRQ ! 1011 + .long do_IRQ ! 1100 + .long do_IRQ ! 1101 + .long do_IRQ ! 1110 + .long exception_error + ! Internal hardware + .long do_IRQ ! TMU0 tuni0 /* 400 */ + .long do_IRQ ! TMU1 tuni1 + .long do_IRQ ! TMU2 tuni2 + .long do_IRQ ! ticpi2 +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long exception_error /* 500 */ + .long exception_error + .long exception_error +#else + .long do_IRQ ! RTC ati + .long do_IRQ ! pri + .long do_IRQ ! cui + .long do_IRQ ! SCI eri + .long do_IRQ ! rxi /* 500 */ + .long do_IRQ ! txi + .long do_IRQ ! tei +#endif + .long do_IRQ ! WDT iti /* 560 */ + .long do_IRQ ! REF rcmi + .long do_IRQ ! rovi + .long do_IRQ + .long do_IRQ /* 5E0 */ + .long do_IRQ ! 32 Hitachi UDI /* 600 */ + .long do_IRQ ! 33 GPIO + .long do_IRQ ! 34 DMAC dmte0 + .long do_IRQ ! 35 dmte1 + .long do_IRQ ! 36 dmte2 + .long do_IRQ ! 37 dmte3 + .long do_IRQ ! 38 dmae + .long exception_error ! 39 /* 6E0 */ +#if defined(CONFIG_CPU_SUBTYPE_SH7760) + .long exception_error /* 700 */ + .long exception_error + .long exception_error + .long exception_error /* 760 */ +#else + .long do_IRQ ! 40 SCIF eri /* 700 */ + .long do_IRQ ! 41 rxi + .long do_IRQ ! 42 bri + .long do_IRQ ! 43 txi +#endif +#if CONFIG_NR_ONCHIP_DMA_CHANNELS == 8 + .long do_IRQ ! 44 DMAC dmte4 /* 780 */ + .long do_IRQ ! 45 dmte5 + .long do_IRQ ! 46 dmte6 + .long do_IRQ ! 47 dmte7 /* 7E0 */ +#else + .long exception_error ! 44 /* 780 */ + .long exception_error ! 45 + .long exception_error ! 46 + .long exception_error ! 47 +#endif +#if defined(CONFIG_SH_FPU) + .long do_fpu_state_restore ! 48 /* 800 */ + .long do_fpu_state_restore ! 49 /* 820 */ +#else + .long exception_error + .long exception_error +#endif +#if defined(CONFIG_CPU_SUBTYPE_SH7751) + .long exception_error /* 840 */ + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long exception_error /* 900 */ + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long do_IRQ ! PCI serr /* A00 */ + .long do_IRQ ! dma3 + .long do_IRQ ! dma2 + .long do_IRQ ! dma1 + .long do_IRQ ! dma0 + .long do_IRQ ! pwon + .long do_IRQ ! pwdwn + .long do_IRQ ! err + .long do_IRQ ! TMU3 tuni3 /* B00 */ + .long exception_error + .long exception_error + .long exception_error + .long do_IRQ ! TMU4 tuni4 /* B80 */ +#elif defined(CONFIG_CPU_SUBTYPE_SH7760) + .long do_IRQ ! IRQ irq6 /* 840 */ + .long do_IRQ ! irq7 + .long do_IRQ ! SCIF eri0 + .long do_IRQ ! rxi0 + .long do_IRQ ! bri0 + .long do_IRQ ! txi0 + .long do_IRQ ! HCAN2 cani0 /* 900 */ + .long do_IRQ ! cani1 + .long do_IRQ ! SSI ssii0 + .long do_IRQ ! ssii1 + .long do_IRQ ! HAC haci0 + .long do_IRQ ! haci1 + .long do_IRQ ! IIC iici0 + .long do_IRQ ! iici1 + .long do_IRQ ! USB usbi /* A00 */ + .long do_IRQ ! LCDC vint + .long exception_error + .long exception_error + .long do_IRQ ! DMABRG dmabrgi0 + .long do_IRQ ! dmabrgi1 + .long do_IRQ ! dmabrgi2 + .long exception_error + .long do_IRQ ! SCIF eri1 /* B00 */ + .long do_IRQ ! rxi1 + .long do_IRQ ! bri1 + .long do_IRQ ! txi1 + .long do_IRQ ! eri2 + .long do_IRQ ! rxi2 + .long do_IRQ ! bri2 + .long do_IRQ ! txi2 + .long do_IRQ ! SIM simeri /* C00 */ + .long do_IRQ ! simrxi + .long do_IRQ ! simtxi + .long do_IRQ ! simtei + .long do_IRQ ! HSPI spii + .long exception_error + .long exception_error + .long exception_error + .long do_IRQ ! MMCIF mmci0 /* D00 */ + .long do_IRQ ! mmci1 + .long do_IRQ ! mmci2 + .long do_IRQ ! mmci3 + .long exception_error + .long exception_error + .long exception_error + .long exception_error + .long exception_error /* E00 */ + .long exception_error + .long exception_error + .long exception_error + .long do_IRQ ! MFI mfii + .long exception_error + .long exception_error + .long exception_error + .long exception_error /* F00 */ + .long exception_error + .long exception_error + .long exception_error + .long do_IRQ ! ADC adi + .long do_IRQ ! CMT cmti /* FA0 */ +#elif defined(CONFIG_CPU_SUBTYPE_SH73180) + .long do_IRQ ! 50 0x840 + .long do_IRQ ! 51 0x860 + .long do_IRQ ! 52 0x880 + .long do_IRQ ! 53 0x8a0 + .long do_IRQ ! 54 0x8c0 + .long do_IRQ ! 55 0x8e0 + .long do_IRQ ! 56 0x900 + .long do_IRQ ! 57 0x920 + .long do_IRQ ! 58 0x940 + .long do_IRQ ! 59 0x960 + .long do_IRQ ! 60 0x980 + .long do_IRQ ! 61 0x9a0 + .long do_IRQ ! 62 0x9c0 + .long do_IRQ ! 63 0x9e0 + .long do_IRQ ! 64 0xa00 + .long do_IRQ ! 65 0xa20 + .long do_IRQ ! 66 0xa40 + .long do_IRQ ! 67 0xa60 + .long do_IRQ ! 68 0xa80 + .long do_IRQ ! 69 0xaa0 + .long do_IRQ ! 70 0xac0 + .long do_IRQ ! 71 0xae0 + .long do_IRQ ! 72 0xb00 + .long do_IRQ ! 73 0xb20 + .long do_IRQ ! 74 0xb40 + .long do_IRQ ! 75 0xb60 + .long do_IRQ ! 76 0xb80 + .long do_IRQ ! 77 0xba0 + .long do_IRQ ! 78 0xbc0 + .long do_IRQ ! 79 0xbe0 + .long do_IRQ ! 80 0xc00 + .long do_IRQ ! 81 0xc20 + .long do_IRQ ! 82 0xc40 + .long do_IRQ ! 83 0xc60 + .long do_IRQ ! 84 0xc80 + .long do_IRQ ! 85 0xca0 + .long do_IRQ ! 86 0xcc0 + .long do_IRQ ! 87 0xce0 + .long do_IRQ ! 88 0xd00 + .long do_IRQ ! 89 0xd20 + .long do_IRQ ! 90 0xd40 + .long do_IRQ ! 91 0xd60 + .long do_IRQ ! 92 0xd80 + .long do_IRQ ! 93 0xda0 + .long do_IRQ ! 94 0xdc0 + .long do_IRQ ! 95 0xde0 + .long do_IRQ ! 96 0xe00 + .long do_IRQ ! 97 0xe20 + .long do_IRQ ! 98 0xe40 + .long do_IRQ ! 99 0xe60 + .long do_IRQ ! 100 0xe80 + .long do_IRQ ! 101 0xea0 + .long do_IRQ ! 102 0xec0 + .long do_IRQ ! 103 0xee0 + .long do_IRQ ! 104 0xf00 + .long do_IRQ ! 105 0xf20 + .long do_IRQ ! 106 0xf40 + .long do_IRQ ! 107 0xf60 + .long do_IRQ ! 108 0xf80 +#elif defined(CONFIG_CPU_SUBTYPE_ST40STB1) + .long exception_error ! 50 0x840 + .long exception_error ! 51 0x860 + .long exception_error ! 52 0x880 + .long exception_error ! 53 0x8a0 + .long exception_error ! 54 0x8c0 + .long exception_error ! 55 0x8e0 + .long exception_error ! 56 0x900 + .long exception_error ! 57 0x920 + .long exception_error ! 58 0x940 + .long exception_error ! 59 0x960 + .long exception_error ! 60 0x980 + .long exception_error ! 61 0x9a0 + .long exception_error ! 62 0x9c0 + .long exception_error ! 63 0x9e0 + .long do_IRQ ! 64 0xa00 PCI serr + .long do_IRQ ! 65 0xa20 err + .long do_IRQ ! 66 0xa40 ad + .long do_IRQ ! 67 0xa60 pwr_dwn + .long exception_error ! 68 0xa80 + .long exception_error ! 69 0xaa0 + .long exception_error ! 70 0xac0 + .long exception_error ! 71 0xae0 + .long do_IRQ ! 72 0xb00 DMA INT0 + .long do_IRQ ! 73 0xb20 INT1 + .long do_IRQ ! 74 0xb40 INT2 + .long do_IRQ ! 75 0xb60 INT3 + .long do_IRQ ! 76 0xb80 INT4 + .long exception_error ! 77 0xba0 + .long do_IRQ ! 78 0xbc0 DMA ERR + .long exception_error ! 79 0xbe0 + .long do_IRQ ! 80 0xc00 PIO0 + .long do_IRQ ! 81 0xc20 PIO1 + .long do_IRQ ! 82 0xc40 PIO2 + .long exception_error ! 83 0xc60 + .long exception_error ! 84 0xc80 + .long exception_error ! 85 0xca0 + .long exception_error ! 86 0xcc0 + .long exception_error ! 87 0xce0 + .long exception_error ! 88 0xd00 + .long exception_error ! 89 0xd20 + .long exception_error ! 90 0xd40 + .long exception_error ! 91 0xd60 + .long exception_error ! 92 0xd80 + .long exception_error ! 93 0xda0 + .long exception_error ! 94 0xdc0 + .long exception_error ! 95 0xde0 + .long exception_error ! 96 0xe00 + .long exception_error ! 97 0xe20 + .long exception_error ! 98 0xe40 + .long exception_error ! 99 0xe60 + .long exception_error ! 100 0xe80 + .long exception_error ! 101 0xea0 + .long exception_error ! 102 0xec0 + .long exception_error ! 103 0xee0 + .long exception_error ! 104 0xf00 + .long exception_error ! 105 0xf20 + .long exception_error ! 106 0xf40 + .long exception_error ! 107 0xf60 + .long exception_error ! 108 0xf80 + .long exception_error ! 109 0xfa0 + .long exception_error ! 110 0xfc0 + .long exception_error ! 111 0xfe0 + .long do_IRQ ! 112 0x1000 Mailbox + .long exception_error ! 113 0x1020 + .long exception_error ! 114 0x1040 + .long exception_error ! 115 0x1060 + .long exception_error ! 116 0x1080 + .long exception_error ! 117 0x10a0 + .long exception_error ! 118 0x10c0 + .long exception_error ! 119 0x10e0 + .long exception_error ! 120 0x1100 + .long exception_error ! 121 0x1120 + .long exception_error ! 122 0x1140 + .long exception_error ! 123 0x1160 + .long exception_error ! 124 0x1180 + .long exception_error ! 125 0x11a0 + .long exception_error ! 126 0x11c0 + .long exception_error ! 127 0x11e0 + .long exception_error ! 128 0x1200 + .long exception_error ! 129 0x1220 + .long exception_error ! 130 0x1240 + .long exception_error ! 131 0x1260 + .long exception_error ! 132 0x1280 + .long exception_error ! 133 0x12a0 + .long exception_error ! 134 0x12c0 + .long exception_error ! 135 0x12e0 + .long exception_error ! 136 0x1300 + .long exception_error ! 137 0x1320 + .long exception_error ! 138 0x1340 + .long exception_error ! 139 0x1360 + .long do_IRQ ! 140 0x1380 EMPI INV_ADDR + .long exception_error ! 141 0x13a0 + .long exception_error ! 142 0x13c0 + .long exception_error ! 143 0x13e0 +#endif + diff --git a/arch/sh/kernel/cpu/sh4/fpu.c b/arch/sh/kernel/cpu/sh4/fpu.c new file mode 100644 index 000000000000..f486c07e10e2 --- /dev/null +++ b/arch/sh/kernel/cpu/sh4/fpu.c @@ -0,0 +1,335 @@ +/* $Id: fpu.c,v 1.4 2004/01/13 05:52:11 kkojima Exp $ + * + * linux/arch/sh/kernel/fpu.c + * + * Save/restore floating point context for signal handlers. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka + * + * FIXME! These routines can be optimized in big endian case. + */ + +#include <linux/sched.h> +#include <linux/signal.h> +#include <asm/processor.h> +#include <asm/io.h> + +/* The PR (precision) bit in the FP Status Register must be clear when + * an frchg instruction is executed, otherwise the instruction is undefined. + * Executing frchg with PR set causes a trap on some SH4 implementations. + */ + +#define FPSCR_RCHG 0x00000000 + + +/* + * Save FPU registers onto task structure. + * Assume called with FPU enabled (SR.FD=0). + */ +void +save_fpu(struct task_struct *tsk, struct pt_regs *regs) +{ + unsigned long dummy; + + clear_tsk_thread_flag(tsk, TIF_USEDFPU); + enable_fpu(); + asm volatile("sts.l fpul, @-%0\n\t" + "sts.l fpscr, @-%0\n\t" + "lds %2, fpscr\n\t" + "frchg\n\t" + "fmov.s fr15, @-%0\n\t" + "fmov.s fr14, @-%0\n\t" + "fmov.s fr13, @-%0\n\t" + "fmov.s fr12, @-%0\n\t" + "fmov.s fr11, @-%0\n\t" + "fmov.s fr10, @-%0\n\t" + "fmov.s fr9, @-%0\n\t" + "fmov.s fr8, @-%0\n\t" + "fmov.s fr7, @-%0\n\t" + "fmov.s fr6, @-%0\n\t" + "fmov.s fr5, @-%0\n\t" + "fmov.s fr4, @-%0\n\t" + "fmov.s fr3, @-%0\n\t" + "fmov.s fr2, @-%0\n\t" + "fmov.s fr1, @-%0\n\t" + "fmov.s fr0, @-%0\n\t" + "frchg\n\t" + "fmov.s fr15, @-%0\n\t" + "fmov.s fr14, @-%0\n\t" + "fmov.s fr13, @-%0\n\t" + "fmov.s fr12, @-%0\n\t" + "fmov.s fr11, @-%0\n\t" + "fmov.s fr10, @-%0\n\t" + "fmov.s fr9, @-%0\n\t" + "fmov.s fr8, @-%0\n\t" + "fmov.s fr7, @-%0\n\t" + "fmov.s fr6, @-%0\n\t" + "fmov.s fr5, @-%0\n\t" + "fmov.s fr4, @-%0\n\t" + "fmov.s fr3, @-%0\n\t" + "fmov.s fr2, @-%0\n\t" + "fmov.s fr1, @-%0\n\t" + "fmov.s fr0, @-%0\n\t" + "lds %3, fpscr\n\t" + : "=r" (dummy) + : "0" ((char *)(&tsk->thread.fpu.hard.status)), + "r" (FPSCR_RCHG), + "r" (FPSCR_INIT) + : "memory"); + + disable_fpu(); + release_fpu(regs); +} + +static void +restore_fpu(struct task_struct *tsk) +{ + unsigned long dummy; + + enable_fpu(); + asm volatile("lds %2, fpscr\n\t" + "fmov.s @%0+, fr0\n\t" + "fmov.s @%0+, fr1\n\t" + "fmov.s @%0+, fr2\n\t" + "fmov.s @%0+, fr3\n\t" + "fmov.s @%0+, fr4\n\t" + "fmov.s @%0+, fr5\n\t" + "fmov.s @%0+, fr6\n\t" + "fmov.s @%0+, fr7\n\t" + "fmov.s @%0+, fr8\n\t" + "fmov.s @%0+, fr9\n\t" + "fmov.s @%0+, fr10\n\t" + "fmov.s @%0+, fr11\n\t" + "fmov.s @%0+, fr12\n\t" + "fmov.s @%0+, fr13\n\t" + "fmov.s @%0+, fr14\n\t" + "fmov.s @%0+, fr15\n\t" + "frchg\n\t" + "fmov.s @%0+, fr0\n\t" + "fmov.s @%0+, fr1\n\t" + "fmov.s @%0+, fr2\n\t" + "fmov.s @%0+, fr3\n\t" + "fmov.s @%0+, fr4\n\t" + "fmov.s @%0+, fr5\n\t" + "fmov.s @%0+, fr6\n\t" + "fmov.s @%0+, fr7\n\t" + "fmov.s @%0+, fr8\n\t" + "fmov.s @%0+, fr9\n\t" + "fmov.s @%0+, fr10\n\t" + "fmov.s @%0+, fr11\n\t" + "fmov.s @%0+, fr12\n\t" + "fmov.s @%0+, fr13\n\t" + "fmov.s @%0+, fr14\n\t" + "fmov.s @%0+, fr15\n\t" + "frchg\n\t" + "lds.l @%0+, fpscr\n\t" + "lds.l @%0+, fpul\n\t" + : "=r" (dummy) + : "0" (&tsk->thread.fpu), "r" (FPSCR_RCHG) + : "memory"); + disable_fpu(); +} + +/* + * Load the FPU with signalling NANS. This bit pattern we're using + * has the property that no matter wether considered as single or as + * double precission represents signaling NANS. + */ + +static void +fpu_init(void) +{ + enable_fpu(); + asm volatile("lds %0, fpul\n\t" + "lds %1, fpscr\n\t" + "fsts fpul, fr0\n\t" + "fsts fpul, fr1\n\t" + "fsts fpul, fr2\n\t" + "fsts fpul, fr3\n\t" + "fsts fpul, fr4\n\t" + "fsts fpul, fr5\n\t" + "fsts fpul, fr6\n\t" + "fsts fpul, fr7\n\t" + "fsts fpul, fr8\n\t" + "fsts fpul, fr9\n\t" + "fsts fpul, fr10\n\t" + "fsts fpul, fr11\n\t" + "fsts fpul, fr12\n\t" + "fsts fpul, fr13\n\t" + "fsts fpul, fr14\n\t" + "fsts fpul, fr15\n\t" + "frchg\n\t" + "fsts fpul, fr0\n\t" + "fsts fpul, fr1\n\t" + "fsts fpul, fr2\n\t" + "fsts fpul, fr3\n\t" + "fsts fpul, fr4\n\t" + "fsts fpul, fr5\n\t" + "fsts fpul, fr6\n\t" + "fsts fpul, fr7\n\t" + "fsts fpul, fr8\n\t" + "fsts fpul, fr9\n\t" + "fsts fpul, fr10\n\t" + "fsts fpul, fr11\n\t" + "fsts fpul, fr12\n\t" + "fsts fpul, fr13\n\t" + "fsts fpul, fr14\n\t" + "fsts fpul, fr15\n\t" + "frchg\n\t" + "lds %2, fpscr\n\t" + : /* no output */ + : "r" (0), "r" (FPSCR_RCHG), "r" (FPSCR_INIT)); + disable_fpu(); +} + +/** + * denormal_to_double - Given denormalized float number, + * store double float + * + * @fpu: Pointer to sh_fpu_hard structure + * @n: Index to FP register + */ +static void +denormal_to_double (struct sh_fpu_hard_struct *fpu, int n) +{ + unsigned long du, dl; + unsigned long x = fpu->fpul; + int exp = 1023 - 126; + + if (x != 0 && (x & 0x7f800000) == 0) { + du = (x & 0x80000000); + while ((x & 0x00800000) == 0) { + x <<= 1; + exp--; + } + x &= 0x007fffff; + du |= (exp << 20) | (x >> 3); + dl = x << 29; + + fpu->fp_regs[n] = du; + fpu->fp_regs[n+1] = dl; + } +} + +/** + * ieee_fpe_handler - Handle denormalized number exception + * + * @regs: Pointer to register structure + * + * Returns 1 when it's handled (should not cause exception). + */ +static int +ieee_fpe_handler (struct pt_regs *regs) +{ + unsigned short insn = *(unsigned short *) regs->pc; + unsigned short finsn; + unsigned long nextpc; + int nib[4] = { + (insn >> 12) & 0xf, + (insn >> 8) & 0xf, + (insn >> 4) & 0xf, + insn & 0xf}; + + if (nib[0] == 0xb || + (nib[0] == 0x4 && nib[2] == 0x0 && nib[3] == 0xb)) /* bsr & jsr */ + regs->pr = regs->pc + 4; + + if (nib[0] == 0xa || nib[0] == 0xb) { /* bra & bsr */ + nextpc = regs->pc + 4 + ((short) ((insn & 0xfff) << 4) >> 3); + finsn = *(unsigned short *) (regs->pc + 2); + } else if (nib[0] == 0x8 && nib[1] == 0xd) { /* bt/s */ + if (regs->sr & 1) + nextpc = regs->pc + 4 + ((char) (insn & 0xff) << 1); + else + nextpc = regs->pc + 4; + finsn = *(unsigned short *) (regs->pc + 2); + } else if (nib[0] == 0x8 && nib[1] == 0xf) { /* bf/s */ + if (regs->sr & 1) + nextpc = regs->pc + 4; + else + nextpc = regs->pc + 4 + ((char) (insn & 0xff) << 1); + finsn = *(unsigned short *) (regs->pc + 2); + } else if (nib[0] == 0x4 && nib[3] == 0xb && + (nib[2] == 0x0 || nib[2] == 0x2)) { /* jmp & jsr */ + nextpc = regs->regs[nib[1]]; + finsn = *(unsigned short *) (regs->pc + 2); + } else if (nib[0] == 0x0 && nib[3] == 0x3 && + (nib[2] == 0x0 || nib[2] == 0x2)) { /* braf & bsrf */ + nextpc = regs->pc + 4 + regs->regs[nib[1]]; + finsn = *(unsigned short *) (regs->pc + 2); + } else if (insn == 0x000b) { /* rts */ + nextpc = regs->pr; + finsn = *(unsigned short *) (regs->pc + 2); + } else { + nextpc = regs->pc + 2; + finsn = insn; + } + + if ((finsn & 0xf1ff) == 0xf0ad) { /* fcnvsd */ + struct task_struct *tsk = current; + + save_fpu(tsk, regs); + if ((tsk->thread.fpu.hard.fpscr & (1 << 17))) { + /* FPU error */ + denormal_to_double (&tsk->thread.fpu.hard, + (finsn >> 8) & 0xf); + tsk->thread.fpu.hard.fpscr &= + ~(FPSCR_CAUSE_MASK | FPSCR_FLAG_MASK); + grab_fpu(regs); + restore_fpu(tsk); + set_tsk_thread_flag(tsk, TIF_USEDFPU); + } else { + tsk->thread.trap_no = 11; + tsk->thread.error_code = 0; + force_sig(SIGFPE, tsk); + } + + regs->pc = nextpc; + return 1; + } + + return 0; +} + +asmlinkage void +do_fpu_error(unsigned long r4, unsigned long r5, unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + struct task_struct *tsk = current; + + if (ieee_fpe_handler (®s)) + return; + + regs.pc += 2; + save_fpu(tsk, ®s); + tsk->thread.trap_no = 11; + tsk->thread.error_code = 0; + force_sig(SIGFPE, tsk); +} + +asmlinkage void +do_fpu_state_restore(unsigned long r4, unsigned long r5, unsigned long r6, + unsigned long r7, struct pt_regs regs) +{ + struct task_struct *tsk = current; + + grab_fpu(®s); + if (!user_mode(®s)) { + printk(KERN_ERR "BUG: FPU is used in kernel mode.\n"); + return; + } + + if (used_math()) { + /* Using the FPU again. */ + restore_fpu(tsk); + } else { + /* First time FPU user. */ + fpu_init(); + set_used_math(); + } + set_tsk_thread_flag(tsk, TIF_USEDFPU); +} diff --git a/arch/sh/kernel/cpu/sh4/irq_intc2.c b/arch/sh/kernel/cpu/sh4/irq_intc2.c new file mode 100644 index 000000000000..099ebbf89745 --- /dev/null +++ b/arch/sh/kernel/cpu/sh4/irq_intc2.c @@ -0,0 +1,222 @@ +/* + * linux/arch/sh/kernel/irq_intc2.c + * + * 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. + * + * Interrupt handling for INTC2-based IRQ. + * + * These are the "new Hitachi style" interrupts, as present on the + * Hitachi 7751 and the STM ST40 STB1. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/irq.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/machvec.h> + + +struct intc2_data { + unsigned char msk_offset; + unsigned char msk_shift; +#ifdef CONFIG_CPU_SUBTYPE_ST40 + int (*clear_irq) (int); +#endif +}; + + +static struct intc2_data intc2_data[NR_INTC2_IRQS]; + +static void enable_intc2_irq(unsigned int irq); +static void disable_intc2_irq(unsigned int irq); + +/* shutdown is same as "disable" */ +#define shutdown_intc2_irq disable_intc2_irq + +static void mask_and_ack_intc2(unsigned int); +static void end_intc2_irq(unsigned int irq); + +static unsigned int startup_intc2_irq(unsigned int irq) +{ + enable_intc2_irq(irq); + return 0; /* never anything pending */ +} + +static struct hw_interrupt_type intc2_irq_type = { + "INTC2-IRQ", + startup_intc2_irq, + shutdown_intc2_irq, + enable_intc2_irq, + disable_intc2_irq, + mask_and_ack_intc2, + end_intc2_irq +}; + +static void disable_intc2_irq(unsigned int irq) +{ + int irq_offset = irq - INTC2_FIRST_IRQ; + int msk_shift, msk_offset; + + // Sanity check + if((irq_offset<0) || (irq_offset>=NR_INTC2_IRQS)) + return; + + msk_shift = intc2_data[irq_offset].msk_shift; + msk_offset = intc2_data[irq_offset].msk_offset; + + ctrl_outl(1<<msk_shift, + INTC2_BASE+INTC2_INTMSK_OFFSET+msk_offset); +} + +static void enable_intc2_irq(unsigned int irq) +{ + int irq_offset = irq - INTC2_FIRST_IRQ; + int msk_shift, msk_offset; + + /* Sanity check */ + if((irq_offset<0) || (irq_offset>=NR_INTC2_IRQS)) + return; + + msk_shift = intc2_data[irq_offset].msk_shift; + msk_offset = intc2_data[irq_offset].msk_offset; + + ctrl_outl(1<<msk_shift, + INTC2_BASE+INTC2_INTMSKCLR_OFFSET+msk_offset); +} + +static void mask_and_ack_intc2(unsigned int irq) +{ + disable_intc2_irq(irq); +} + +static void end_intc2_irq(unsigned int irq) +{ + if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) + enable_intc2_irq(irq); + +#ifdef CONFIG_CPU_SUBTYPE_ST40 + if (intc2_data[irq - INTC2_FIRST_IRQ].clear_irq) + intc2_data[irq - INTC2_FIRST_IRQ].clear_irq (irq); +#endif +} + +/* + * Setup an INTC2 style interrupt. + * NOTE: Unlike IPR interrupts, parameters are not shifted by this code, + * allowing the use of the numbers straight out of the datasheet. + * For example: + * PIO1 which is INTPRI00[19,16] and INTMSK00[13] + * would be: ^ ^ ^ ^ + * | | | | + * make_intc2_irq(84, 0, 16, 0, 13); + */ +void make_intc2_irq(unsigned int irq, + unsigned int ipr_offset, unsigned int ipr_shift, + unsigned int msk_offset, unsigned int msk_shift, + unsigned int priority) +{ + int irq_offset = irq - INTC2_FIRST_IRQ; + unsigned int flags; + unsigned long ipr; + + if((irq_offset<0) || (irq_offset>=NR_INTC2_IRQS)) + return; + + disable_irq_nosync(irq); + + /* Fill the data we need */ + intc2_data[irq_offset].msk_offset = msk_offset; + intc2_data[irq_offset].msk_shift = msk_shift; +#ifdef CONFIG_CPU_SUBTYPE_ST40 + intc2_data[irq_offset].clear_irq = NULL; +#endif + + /* Set the priority level */ + local_irq_save(flags); + + ipr=ctrl_inl(INTC2_BASE+INTC2_INTPRI_OFFSET+ipr_offset); + ipr&=~(0xf<<ipr_shift); + ipr|=(priority)<<ipr_shift; + ctrl_outl(ipr, INTC2_BASE+INTC2_INTPRI_OFFSET+ipr_offset); + + local_irq_restore(flags); + + irq_desc[irq].handler=&intc2_irq_type; + + disable_intc2_irq(irq); +} + +#ifdef CONFIG_CPU_SUBTYPE_ST40 + +struct intc2_init { + unsigned short irq; + unsigned char ipr_offset, ipr_shift; + unsigned char msk_offset, msk_shift; +}; + +static struct intc2_init intc2_init_data[] __initdata = { + {64, 0, 0, 0, 0}, /* PCI serr */ + {65, 0, 4, 0, 1}, /* PCI err */ + {66, 0, 4, 0, 2}, /* PCI ad */ + {67, 0, 4, 0, 3}, /* PCI pwd down */ + {72, 0, 8, 0, 5}, /* DMAC INT0 */ + {73, 0, 8, 0, 6}, /* DMAC INT1 */ + {74, 0, 8, 0, 7}, /* DMAC INT2 */ + {75, 0, 8, 0, 8}, /* DMAC INT3 */ + {76, 0, 8, 0, 9}, /* DMAC INT4 */ + {78, 0, 8, 0, 11}, /* DMAC ERR */ + {80, 0, 12, 0, 12}, /* PIO0 */ + {84, 0, 16, 0, 13}, /* PIO1 */ + {88, 0, 20, 0, 14}, /* PIO2 */ + {112, 4, 0, 4, 0}, /* Mailbox */ +#ifdef CONFIG_CPU_SUBTYPE_ST40GX1 + {116, 4, 4, 4, 4}, /* SSC0 */ + {120, 4, 8, 4, 8}, /* IR Blaster */ + {124, 4, 12, 4, 12}, /* USB host */ + {128, 4, 16, 4, 16}, /* Video processor BLITTER */ + {132, 4, 20, 4, 20}, /* UART0 */ + {134, 4, 20, 4, 22}, /* UART2 */ + {136, 4, 24, 4, 24}, /* IO_PIO0 */ + {140, 4, 28, 4, 28}, /* EMPI */ + {144, 8, 0, 8, 0}, /* MAFE */ + {148, 8, 4, 8, 4}, /* PWM */ + {152, 8, 8, 8, 8}, /* SSC1 */ + {156, 8, 12, 8, 12}, /* IO_PIO1 */ + {160, 8, 16, 8, 16}, /* USB target */ + {164, 8, 20, 8, 20}, /* UART1 */ + {168, 8, 24, 8, 24}, /* Teletext */ + {172, 8, 28, 8, 28}, /* VideoSync VTG */ + {173, 8, 28, 8, 29}, /* VideoSync DVP0 */ + {174, 8, 28, 8, 30}, /* VideoSync DVP1 */ +#endif +}; + +void __init init_IRQ_intc2(void) +{ + struct intc2_init *p; + + printk(KERN_ALERT "init_IRQ_intc2\n"); + + for (p = intc2_init_data; + p<intc2_init_data+ARRAY_SIZE(intc2_init_data); + p++) { + make_intc2_irq(p->irq, p->ipr_offset, p->ipr_shift, + p-> msk_offset, p->msk_shift, 13); + } +} + +/* Adds a termination callback to the interrupt */ +void intc2_add_clear_irq(int irq, int (*fn)(int)) +{ + if (irq < INTC2_FIRST_IRQ) + return; + + intc2_data[irq - INTC2_FIRST_IRQ].clear_irq = fn; +} + +#endif /* CONFIG_CPU_SUBTYPE_ST40 */ diff --git a/arch/sh/kernel/cpu/sh4/probe.c b/arch/sh/kernel/cpu/sh4/probe.c new file mode 100644 index 000000000000..42427b79697b --- /dev/null +++ b/arch/sh/kernel/cpu/sh4/probe.c @@ -0,0 +1,138 @@ +/* + * arch/sh/kernel/cpu/sh4/probe.c + * + * CPU Subtype Probing for SH-4. + * + * Copyright (C) 2001, 2002, 2003, 2004 Paul Mundt + * Copyright (C) 2003 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/init.h> +#include <asm/processor.h> +#include <asm/cache.h> +#include <asm/io.h> + +int __init detect_cpu_and_cache_system(void) +{ + unsigned long pvr, prr, cvr; + unsigned long size; + + static unsigned long sizes[16] = { + [1] = (1 << 12), + [2] = (1 << 13), + [4] = (1 << 14), + [8] = (1 << 15), + [9] = (1 << 16) + }; + + pvr = (ctrl_inl(CCN_PVR) >> 8) & 0xffff; + prr = (ctrl_inl(CCN_PRR) >> 4) & 0xff; + cvr = (ctrl_inl(CCN_CVR)); + + /* + * Setup some sane SH-4 defaults for the icache + */ + cpu_data->icache.way_incr = (1 << 13); + cpu_data->icache.entry_shift = 5; + cpu_data->icache.entry_mask = 0x1fe0; + cpu_data->icache.sets = 256; + cpu_data->icache.ways = 1; + cpu_data->icache.linesz = L1_CACHE_BYTES; + + /* + * And again for the dcache .. + */ + cpu_data->dcache.way_incr = (1 << 14); + cpu_data->dcache.entry_shift = 5; + cpu_data->dcache.entry_mask = 0x3fe0; + cpu_data->dcache.sets = 512; + cpu_data->dcache.ways = 1; + cpu_data->dcache.linesz = L1_CACHE_BYTES; + + /* Set the FPU flag, virtually all SH-4's have one */ + cpu_data->flags |= CPU_HAS_FPU; + + /* + * Probe the underlying processor version/revision and + * adjust cpu_data setup accordingly. + */ + switch (pvr) { + case 0x205: + cpu_data->type = CPU_SH7750; + cpu_data->flags |= CPU_HAS_P2_FLUSH_BUG | CPU_HAS_PERF_COUNTER; + break; + case 0x206: + cpu_data->type = CPU_SH7750S; + cpu_data->flags |= CPU_HAS_P2_FLUSH_BUG | CPU_HAS_PERF_COUNTER; + break; + case 0x1100: + cpu_data->type = CPU_SH7751; + break; + case 0x2000: + cpu_data->type = CPU_SH73180; + cpu_data->icache.ways = 4; + cpu_data->dcache.ways = 4; + cpu_data->flags &= ~CPU_HAS_FPU; + break; + case 0x8000: + cpu_data->type = CPU_ST40RA; + break; + case 0x8100: + cpu_data->type = CPU_ST40GX1; + break; + case 0x700: + cpu_data->type = CPU_SH4_501; + cpu_data->icache.ways = 2; + cpu_data->dcache.ways = 2; + + /* No FPU on the SH4-500 series.. */ + cpu_data->flags &= ~CPU_HAS_FPU; + break; + case 0x600: + cpu_data->type = CPU_SH4_202; + cpu_data->icache.ways = 2; + cpu_data->dcache.ways = 2; + break; + case 0x500 ... 0x501: + switch (prr) { + case 0x10: cpu_data->type = CPU_SH7750R; break; + case 0x11: cpu_data->type = CPU_SH7751R; break; + case 0x50: cpu_data->type = CPU_SH7760; break; + } + + cpu_data->icache.ways = 2; + cpu_data->dcache.ways = 2; + + break; + default: + cpu_data->type = CPU_SH_NONE; + break; + } + + /* + * On anything that's not a direct-mapped cache, look to the CVR + * for I/D-cache specifics. + */ + if (cpu_data->icache.ways > 1) { + size = sizes[(cvr >> 20) & 0xf]; + cpu_data->icache.way_incr = (size >> 1); + cpu_data->icache.sets = (size >> 6); + cpu_data->icache.entry_mask = + (cpu_data->icache.way_incr - (1 << 5)); + } + + if (cpu_data->dcache.ways > 1) { + size = sizes[(cvr >> 16) & 0xf]; + cpu_data->dcache.way_incr = (size >> 1); + cpu_data->dcache.sets = (size >> 6); + cpu_data->dcache.entry_mask = + (cpu_data->dcache.way_incr - (1 << 5)); + } + + return 0; +} + diff --git a/arch/sh/kernel/cpu/sh4/sq.c b/arch/sh/kernel/cpu/sh4/sq.c new file mode 100644 index 000000000000..8437ea7430fe --- /dev/null +++ b/arch/sh/kernel/cpu/sh4/sq.c @@ -0,0 +1,453 @@ +/* + * arch/sh/kernel/cpu/sq.c + * + * General management API for SH-4 integrated Store Queues + * + * Copyright (C) 2001, 2002, 2003, 2004 Paul Mundt + * Copyright (C) 2001, 2002 M. R. Brown + * + * Some of this code has been adopted directly from the old arch/sh/mm/sq.c + * hack that was part of the LinuxDC project. For all intents and purposes, + * this is a completely new interface that really doesn't have much in common + * with the old zone-based approach at all. In fact, it's only listed here for + * general completeness. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/config.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/proc_fs.h> +#include <linux/miscdevice.h> +#include <linux/vmalloc.h> + +#include <asm/io.h> +#include <asm/page.h> +#include <asm/mmu_context.h> +#include <asm/cpu/sq.h> + +static LIST_HEAD(sq_mapping_list); +static DEFINE_SPINLOCK(sq_mapping_lock); + +/** + * sq_flush - Flush (prefetch) the store queue cache + * @addr: the store queue address to flush + * + * Executes a prefetch instruction on the specified store queue cache, + * so that the cached data is written to physical memory. + */ +inline void sq_flush(void *addr) +{ + __asm__ __volatile__ ("pref @%0" : : "r" (addr) : "memory"); +} + +/** + * sq_flush_range - Flush (prefetch) a specific SQ range + * @start: the store queue address to start flushing from + * @len: the length to flush + * + * Flushes the store queue cache from @start to @start + @len in a + * linear fashion. + */ +void sq_flush_range(unsigned long start, unsigned int len) +{ + volatile unsigned long *sq = (unsigned long *)start; + unsigned long dummy; + + /* Flush the queues */ + for (len >>= 5; len--; sq += 8) + sq_flush((void *)sq); + + /* Wait for completion */ + dummy = ctrl_inl(P4SEG_STORE_QUE); + + ctrl_outl(0, P4SEG_STORE_QUE + 0); + ctrl_outl(0, P4SEG_STORE_QUE + 8); +} + +static struct sq_mapping *__sq_alloc_mapping(unsigned long virt, unsigned long phys, unsigned long size, const char *name) +{ + struct sq_mapping *map; + + if (virt + size > SQ_ADDRMAX) + return ERR_PTR(-ENOSPC); + + map = kmalloc(sizeof(struct sq_mapping), GFP_KERNEL); + if (!map) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&map->list); + + map->sq_addr = virt; + map->addr = phys; + map->size = size + 1; + map->name = name; + + list_add(&map->list, &sq_mapping_list); + + return map; +} + +static unsigned long __sq_get_next_addr(void) +{ + if (!list_empty(&sq_mapping_list)) { + struct list_head *pos, *tmp; + + /* + * Read one off the list head, as it will have the highest + * mapped allocation. Set the next one up right above it. + * + * This is somewhat sub-optimal, as we don't look at + * gaps between allocations or anything lower then the + * highest-level allocation. + * + * However, in the interest of performance and the general + * lack of desire to do constant list rebalancing, we don't + * worry about it. + */ + list_for_each_safe(pos, tmp, &sq_mapping_list) { + struct sq_mapping *entry; + + entry = list_entry(pos, typeof(*entry), list); + + return entry->sq_addr + entry->size; + } + } + + return P4SEG_STORE_QUE; +} + +/** + * __sq_remap - Perform a translation from the SQ to a phys addr + * @map: sq mapping containing phys and store queue addresses. + * + * Maps the store queue address specified in the mapping to the physical + * address specified in the mapping. + */ +static struct sq_mapping *__sq_remap(struct sq_mapping *map) +{ + unsigned long flags, pteh, ptel; + struct vm_struct *vma; + pgprot_t pgprot; + + /* + * Without an MMU (or with it turned off), this is much more + * straightforward, as we can just load up each queue's QACR with + * the physical address appropriately masked. + */ + + ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR0); + ctrl_outl(((map->addr >> 26) << 2) & 0x1c, SQ_QACR1); + +#ifdef CONFIG_MMU + /* + * With an MMU on the other hand, things are slightly more involved. + * Namely, we have to have a direct mapping between the SQ addr and + * the associated physical address in the UTLB by way of setting up + * a virt<->phys translation by hand. We do this by simply specifying + * the SQ addr in UTLB.VPN and the associated physical address in + * UTLB.PPN. + * + * Notably, even though this is a special case translation, and some + * of the configuration bits are meaningless, we're still required + * to have a valid ASID context in PTEH. + * + * We could also probably get by without explicitly setting PTEA, but + * we do it here just for good measure. + */ + spin_lock_irqsave(&sq_mapping_lock, flags); + + pteh = map->sq_addr; + ctrl_outl((pteh & MMU_VPN_MASK) | get_asid(), MMU_PTEH); + + ptel = map->addr & PAGE_MASK; + ctrl_outl(((ptel >> 28) & 0xe) | (ptel & 0x1), MMU_PTEA); + + pgprot = pgprot_noncached(PAGE_KERNEL); + + ptel &= _PAGE_FLAGS_HARDWARE_MASK; + ptel |= pgprot_val(pgprot); + ctrl_outl(ptel, MMU_PTEL); + + __asm__ __volatile__ ("ldtlb" : : : "memory"); + + spin_unlock_irqrestore(&sq_mapping_lock, flags); + + /* + * Next, we need to map ourselves in the kernel page table, so that + * future accesses after a TLB flush will be handled when we take a + * page fault. + * + * Theoretically we could just do this directly and not worry about + * setting up the translation by hand ahead of time, but for the + * cases where we want a one-shot SQ mapping followed by a quick + * writeout before we hit the TLB flush, we do it anyways. This way + * we at least save ourselves the initial page fault overhead. + */ + vma = __get_vm_area(map->size, VM_ALLOC, map->sq_addr, SQ_ADDRMAX); + if (!vma) + return ERR_PTR(-ENOMEM); + + vma->phys_addr = map->addr; + + if (remap_area_pages((unsigned long)vma->addr, vma->phys_addr, + map->size, pgprot_val(pgprot))) { + vunmap(vma->addr); + return NULL; + } +#endif /* CONFIG_MMU */ + + return map; +} + +/** + * sq_remap - Map a physical address through the Store Queues + * @phys: Physical address of mapping. + * @size: Length of mapping. + * @name: User invoking mapping. + * + * Remaps the physical address @phys through the next available store queue + * address of @size length. @name is logged at boot time as well as through + * the procfs interface. + * + * A pre-allocated and filled sq_mapping pointer is returned, and must be + * cleaned up with a call to sq_unmap() when the user is done with the + * mapping. + */ +struct sq_mapping *sq_remap(unsigned long phys, unsigned int size, const char *name) +{ + struct sq_mapping *map; + unsigned long virt, end; + unsigned int psz; + + /* Don't allow wraparound or zero size */ + end = phys + size - 1; + if (!size || end < phys) + return NULL; + /* Don't allow anyone to remap normal memory.. */ + if (phys < virt_to_phys(high_memory)) + return NULL; + + phys &= PAGE_MASK; + + size = PAGE_ALIGN(end + 1) - phys; + virt = __sq_get_next_addr(); + psz = (size + (PAGE_SIZE - 1)) / PAGE_SIZE; + map = __sq_alloc_mapping(virt, phys, size, name); + + printk("sqremap: %15s [%4d page%s] va 0x%08lx pa 0x%08lx\n", + map->name ? map->name : "???", + psz, psz == 1 ? " " : "s", + map->sq_addr, map->addr); + + return __sq_remap(map); +} + +/** + * sq_unmap - Unmap a Store Queue allocation + * @map: Pre-allocated Store Queue mapping. + * + * Unmaps the store queue allocation @map that was previously created by + * sq_remap(). Also frees up the pte that was previously inserted into + * the kernel page table and discards the UTLB translation. + */ +void sq_unmap(struct sq_mapping *map) +{ + if (map->sq_addr > (unsigned long)high_memory) + vfree((void *)(map->sq_addr & PAGE_MASK)); + + list_del(&map->list); + kfree(map); +} + +/** + * sq_clear - Clear a store queue range + * @addr: Address to start clearing from. + * @len: Length to clear. + * + * A quick zero-fill implementation for clearing out memory that has been + * remapped through the store queues. + */ +void sq_clear(unsigned long addr, unsigned int len) +{ + int i; + + /* Clear out both queues linearly */ + for (i = 0; i < 8; i++) { + ctrl_outl(0, addr + i + 0); + ctrl_outl(0, addr + i + 8); + } + + sq_flush_range(addr, len); +} + +/** + * sq_vma_unmap - Unmap a VMA range + * @area: VMA containing range. + * @addr: Start of range. + * @len: Length of range. + * + * Searches the sq_mapping_list for a mapping matching the sq addr @addr, + * and subsequently frees up the entry. Further cleanup is done by generic + * code. + */ +static void sq_vma_unmap(struct vm_area_struct *area, + unsigned long addr, size_t len) +{ + struct list_head *pos, *tmp; + + list_for_each_safe(pos, tmp, &sq_mapping_list) { + struct sq_mapping *entry; + + entry = list_entry(pos, typeof(*entry), list); + + if (entry->sq_addr == addr) { + /* + * We could probably get away without doing the tlb flush + * here, as generic code should take care of most of this + * when unmapping the rest of the VMA range for us. Leave + * it in for added sanity for the time being.. + */ + __flush_tlb_page(get_asid(), entry->sq_addr & PAGE_MASK); + + list_del(&entry->list); + kfree(entry); + + return; + } + } +} + +/** + * sq_vma_sync - Sync a VMA range + * @area: VMA containing range. + * @start: Start of range. + * @len: Length of range. + * @flags: Additional flags. + * + * Synchronizes an sq mapped range by flushing the store queue cache for + * the duration of the mapping. + * + * Used internally for user mappings, which must use msync() to prefetch + * the store queue cache. + */ +static int sq_vma_sync(struct vm_area_struct *area, + unsigned long start, size_t len, unsigned int flags) +{ + sq_flush_range(start, len); + + return 0; +} + +static struct vm_operations_struct sq_vma_ops = { + .unmap = sq_vma_unmap, + .sync = sq_vma_sync, +}; + +/** + * sq_mmap - mmap() for /dev/cpu/sq + * @file: unused. + * @vma: VMA to remap. + * + * Remap the specified vma @vma through the store queues, and setup associated + * information for the new mapping. Also build up the page tables for the new + * area. + */ +static int sq_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long size = vma->vm_end - vma->vm_start; + struct sq_mapping *map; + + /* + * We're not interested in any arbitrary virtual address that has + * been stuck in the VMA, as we already know what addresses we + * want. Save off the size, and reposition the VMA to begin at + * the next available sq address. + */ + vma->vm_start = __sq_get_next_addr(); + vma->vm_end = vma->vm_start + size; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + vma->vm_flags |= VM_IO | VM_RESERVED; + + map = __sq_alloc_mapping(vma->vm_start, offset, size, "Userspace"); + + if (io_remap_pfn_range(vma, map->sq_addr, map->addr >> PAGE_SHIFT, + size, vma->vm_page_prot)) + return -EAGAIN; + + vma->vm_ops = &sq_vma_ops; + + return 0; +} + +#ifdef CONFIG_PROC_FS +static int sq_mapping_read_proc(char *buf, char **start, off_t off, + int len, int *eof, void *data) +{ + struct list_head *pos; + char *p = buf; + + list_for_each_prev(pos, &sq_mapping_list) { + struct sq_mapping *entry; + + entry = list_entry(pos, typeof(*entry), list); + + p += sprintf(p, "%08lx-%08lx [%08lx]: %s\n", entry->sq_addr, + entry->sq_addr + entry->size - 1, entry->addr, + entry->name); + } + + return p - buf; +} +#endif + +static struct file_operations sq_fops = { + .owner = THIS_MODULE, + .mmap = sq_mmap, +}; + +static struct miscdevice sq_dev = { + .minor = STORE_QUEUE_MINOR, + .name = "sq", + .devfs_name = "cpu/sq", + .fops = &sq_fops, +}; + +static int __init sq_api_init(void) +{ + printk(KERN_NOTICE "sq: Registering store queue API.\n"); + +#ifdef CONFIG_PROC_FS + create_proc_read_entry("sq_mapping", 0, 0, sq_mapping_read_proc, 0); +#endif + + return misc_register(&sq_dev); +} + +static void __exit sq_api_exit(void) +{ + misc_deregister(&sq_dev); +} + +module_init(sq_api_init); +module_exit(sq_api_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>, M. R. Brown <mrbrown@0xd6.org>"); +MODULE_DESCRIPTION("Simple API for SH-4 integrated Store Queues"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(STORE_QUEUE_MINOR); + +EXPORT_SYMBOL(sq_remap); +EXPORT_SYMBOL(sq_unmap); +EXPORT_SYMBOL(sq_clear); +EXPORT_SYMBOL(sq_flush); +EXPORT_SYMBOL(sq_flush_range); + diff --git a/arch/sh/kernel/cpu/ubc.S b/arch/sh/kernel/cpu/ubc.S new file mode 100644 index 000000000000..0c569b20e1c1 --- /dev/null +++ b/arch/sh/kernel/cpu/ubc.S @@ -0,0 +1,59 @@ +/* + * arch/sh/kernel/ubc.S + * + * Set of management routines for the User Break Controller (UBC) + * + * Copyright (C) 2002 Paul Mundt + * + * 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. + */ +#include <linux/linkage.h> +#include <asm/ubc.h> + +#define STBCR2 0xffc00010 + +ENTRY(ubc_sleep) + mov #0, r0 + + mov.l 1f, r1 ! Zero out UBC_BBRA .. + mov.w r0, @r1 + + mov.l 2f, r1 ! .. same for BBRB .. + mov.w r0, @r1 + + mov.l 3f, r1 ! .. and again for BRCR. + mov.w r0, @r1 + + mov.w @r1, r0 ! Dummy read BRCR + + mov.l 4f, r1 ! Set MSTP5 in STBCR2 + mov.b @r1, r0 + or #0x01, r0 + mov.b r0, @r1 + + mov.b @r1, r0 ! Two dummy reads .. + mov.b @r1, r0 + + rts + nop + +ENTRY(ubc_wakeup) + mov.l 4f, r1 ! Clear MSTP5 + mov.b @r1, r0 + and #0xfe, r0 + mov.b r0, @r1 + + mov.b @r1, r0 ! Two more dummy reads .. + mov.b @r1, r0 + + rts + nop + +1: .long UBC_BBRA +2: .long UBC_BBRB +3: .long UBC_BRCR +4: .long STBCR2 + diff --git a/arch/sh/kernel/cpufreq.c b/arch/sh/kernel/cpufreq.c new file mode 100644 index 000000000000..e0b384bef55f --- /dev/null +++ b/arch/sh/kernel/cpufreq.c @@ -0,0 +1,218 @@ +/* + * arch/sh/kernel/cpufreq.c + * + * cpufreq driver for the SuperH processors. + * + * Copyright (C) 2002, 2003, 2004, 2005 Paul Mundt + * Copyright (C) 2002 M. R. Brown + * + * 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. + */ +#include <linux/types.h> +#include <linux/cpufreq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/cpumask.h> +#include <linux/smp.h> + +#include <asm/processor.h> +#include <asm/watchdog.h> +#include <asm/freq.h> +#include <asm/io.h> + +/* + * For SuperH, each policy change requires that we change the IFC, BFC, and + * PFC at the same time. Here we define sane values that won't trash the + * system. + * + * Note the max set is computed at runtime, we use the divisors that we booted + * with to setup our maximum operating frequencies. + */ +struct clock_set { + unsigned int ifc; + unsigned int bfc; + unsigned int pfc; +} clock_sets[] = { +#if defined(CONFIG_CPU_SH3) || defined(CONFIG_CPU_SH2) + { 0, 0, 0 }, /* not implemented yet */ +#elif defined(CONFIG_CPU_SH4) + { 4, 8, 8 }, /* min - IFC: 1/4, BFC: 1/8, PFC: 1/8 */ + { 1, 2, 2 }, /* max - IFC: 1, BFC: 1/2, PFC: 1/2 */ +#endif +}; + +#define MIN_CLOCK_SET 0 +#define MAX_CLOCK_SET (ARRAY_SIZE(clock_sets) - 1) + +/* + * For the time being, we only support two frequencies, which in turn are + * aimed at the POWERSAVE and PERFORMANCE policies, which in turn are derived + * directly from the respective min/max clock sets. Technically we could + * support a wider range of frequencies, but these vary far too much for each + * CPU subtype (and we'd have to construct a frequency table for each subtype). + * + * Maybe something to implement in the future.. + */ +#define SH_FREQ_MAX 0 +#define SH_FREQ_MIN 1 + +static struct cpufreq_frequency_table sh_freqs[] = { + { SH_FREQ_MAX, 0 }, + { SH_FREQ_MIN, 0 }, + { 0, CPUFREQ_TABLE_END }, +}; + +static void sh_cpufreq_update_clocks(unsigned int set) +{ + current_cpu_data.cpu_clock = current_cpu_data.master_clock / clock_sets[set].ifc; + current_cpu_data.bus_clock = current_cpu_data.master_clock / clock_sets[set].bfc; + current_cpu_data.module_clock = current_cpu_data.master_clock / clock_sets[set].pfc; + current_cpu_data.loops_per_jiffy = loops_per_jiffy; +} + +/* XXX: This needs to be split out per CPU and CPU subtype. */ +/* + * Here we notify other drivers of the proposed change and the final change. + */ +static int sh_cpufreq_setstate(unsigned int cpu, unsigned int set) +{ + unsigned short frqcr = ctrl_inw(FRQCR); + cpumask_t cpus_allowed; + struct cpufreq_freqs freqs; + + if (!cpu_online(cpu)) + return -ENODEV; + + cpus_allowed = current->cpus_allowed; + set_cpus_allowed(current, cpumask_of_cpu(cpu)); + + BUG_ON(smp_processor_id() != cpu); + + freqs.cpu = cpu; + freqs.old = current_cpu_data.cpu_clock / 1000; + freqs.new = (current_cpu_data.master_clock / clock_sets[set].ifc) / 1000; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); +#if defined(CONFIG_CPU_SH3) + frqcr |= (newstate & 0x4000) << 14; + frqcr |= (newstate & 0x000c) << 2; +#elif defined(CONFIG_CPU_SH4) + /* + * FRQCR.PLL2EN is 1, we need to allow the PLL to stabilize by + * initializing the WDT. + */ + if (frqcr & (1 << 9)) { + __u8 csr; + + /* + * Set the overflow period to the highest available, + * in this case a 1/4096 division ratio yields a 5.25ms + * overflow period. See asm-sh/watchdog.h for more + * information and a range of other divisors. + */ + csr = sh_wdt_read_csr(); + csr |= WTCSR_CKS_4096; + sh_wdt_write_csr(csr); + + sh_wdt_write_cnt(0); + } + frqcr &= 0x0e00; /* Clear ifc, bfc, pfc */ + frqcr |= get_ifc_value(clock_sets[set].ifc) << 6; + frqcr |= get_bfc_value(clock_sets[set].bfc) << 3; + frqcr |= get_pfc_value(clock_sets[set].pfc); +#endif + ctrl_outw(frqcr, FRQCR); + sh_cpufreq_update_clocks(set); + + set_cpus_allowed(current, cpus_allowed); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static int sh_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + unsigned int min_freq, max_freq; + unsigned int ifc, bfc, pfc; + + if (!cpu_online(policy->cpu)) + return -ENODEV; + + /* Update our maximum clock set */ + get_current_frequency_divisors(&ifc, &bfc, &pfc); + clock_sets[MAX_CLOCK_SET].ifc = ifc; + clock_sets[MAX_CLOCK_SET].bfc = bfc; + clock_sets[MAX_CLOCK_SET].pfc = pfc; + + /* Convert from Hz to kHz */ + max_freq = current_cpu_data.cpu_clock / 1000; + min_freq = (current_cpu_data.master_clock / clock_sets[MIN_CLOCK_SET].ifc) / 1000; + + sh_freqs[SH_FREQ_MAX].frequency = max_freq; + sh_freqs[SH_FREQ_MIN].frequency = min_freq; + + /* cpuinfo and default policy values */ + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + policy->cur = max_freq; + + return cpufreq_frequency_table_cpuinfo(policy, &sh_freqs[0]); +} + +static int sh_cpufreq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, &sh_freqs[0]); +} + +static int sh_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int set, idx = 0; + + if (cpufreq_frequency_table_target(policy, &sh_freqs[0], target_freq, relation, &idx)) + return -EINVAL; + + set = (idx == SH_FREQ_MIN) ? MIN_CLOCK_SET : MAX_CLOCK_SET; + + sh_cpufreq_setstate(policy->cpu, set); + + return 0; +} + +static struct cpufreq_driver sh_cpufreq_driver = { + .owner = THIS_MODULE, + .name = "SH cpufreq", + .init = sh_cpufreq_cpu_init, + .verify = sh_cpufreq_verify, + .target = sh_cpufreq_target, +}; + +static int __init sh_cpufreq_init(void) +{ + if (!current_cpu_data.cpu_clock) + return -EINVAL; + if (cpufreq_register_driver(&sh_cpufreq_driver)) + return -EINVAL; + + return 0; +} + +static void __exit sh_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&sh_cpufreq_driver); +} + +module_init(sh_cpufreq_init); +module_exit(sh_cpufreq_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("cpufreq driver for SuperH"); +MODULE_LICENSE("GPL"); + diff --git a/arch/sh/kernel/early_printk.c b/arch/sh/kernel/early_printk.c new file mode 100644 index 000000000000..1378db375e17 --- /dev/null +++ b/arch/sh/kernel/early_printk.c @@ -0,0 +1,137 @@ +/* + * arch/sh/kernel/early_printk.c + * + * Copyright (C) 1999, 2000 Niibe Yutaka + * Copyright (C) 2002 M. R. Brown + * Copyright (C) 2004 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/console.h> +#include <linux/tty.h> +#include <linux/init.h> +#include <asm/io.h> + +#ifdef CONFIG_SH_STANDARD_BIOS +#include <asm/sh_bios.h> + +/* + * Print a string through the BIOS + */ +static void sh_console_write(struct console *co, const char *s, + unsigned count) +{ + sh_bios_console_write(s, count); +} + +/* + * Setup initial baud/bits/parity. We do two things here: + * - construct a cflag setting for the first rs_open() + * - initialize the serial port + * Return non-zero if we didn't find a serial port. + */ +static int __init sh_console_setup(struct console *co, char *options) +{ + int cflag = CREAD | HUPCL | CLOCAL; + + /* + * Now construct a cflag setting. + * TODO: this is a totally bogus cflag, as we have + * no idea what serial settings the BIOS is using, or + * even if its using the serial port at all. + */ + cflag |= B115200 | CS8 | /*no parity*/0; + + co->cflag = cflag; + + return 0; +} + +static struct console early_console = { + .name = "bios", + .write = sh_console_write, + .setup = sh_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; +#endif + +#ifdef CONFIG_EARLY_SCIF_CONSOLE +#define SCIF_REG 0xffe80000 + +static void scif_sercon_putc(int c) +{ + while (!(ctrl_inw(SCIF_REG + 0x10) & 0x20)) ; + + ctrl_outb(c, SCIF_REG + 12); + ctrl_outw((ctrl_inw(SCIF_REG + 0x10) & 0x9f), SCIF_REG + 0x10); + + if (c == '\n') + scif_sercon_putc('\r'); +} + +static void scif_sercon_flush(void) +{ + ctrl_outw((ctrl_inw(SCIF_REG + 0x10) & 0xbf), SCIF_REG + 0x10); + + while (!(ctrl_inw(SCIF_REG + 0x10) & 0x40)) ; + + ctrl_outw((ctrl_inw(SCIF_REG + 0x10) & 0xbf), SCIF_REG + 0x10); +} + +static void scif_sercon_write(struct console *con, const char *s, unsigned count) +{ + while (count-- > 0) + scif_sercon_putc(*s++); + + scif_sercon_flush(); +} + +static int __init scif_sercon_setup(struct console *con, char *options) +{ + con->cflag = CREAD | HUPCL | CLOCAL | B115200 | CS8; + + return 0; +} + +static struct console early_console = { + .name = "sercon", + .write = scif_sercon_write, + .setup = scif_sercon_setup, + .flags = CON_PRINTBUFFER, + .index = -1, +}; + +void scif_sercon_init(int baud) +{ + ctrl_outw(0, SCIF_REG + 8); + ctrl_outw(0, SCIF_REG); + + /* Set baud rate */ + ctrl_outb((CONFIG_SH_PCLK_FREQ + 16 * baud) / + (32 * baud) - 1, SCIF_REG + 4); + + ctrl_outw(12, SCIF_REG + 24); + ctrl_outw(8, SCIF_REG + 24); + ctrl_outw(0, SCIF_REG + 32); + ctrl_outw(0x60, SCIF_REG + 16); + ctrl_outw(0, SCIF_REG + 36); + ctrl_outw(0x30, SCIF_REG + 8); +} +#endif + +void __init enable_early_printk(void) +{ +#ifdef CONFIG_EARLY_SCIF_CONSOLE + scif_sercon_init(115200); +#endif + register_console(&early_console); +} + +void disable_early_printk(void) +{ + unregister_console(&early_console); +} + diff --git a/arch/sh/kernel/entry.S b/arch/sh/kernel/entry.S new file mode 100644 index 000000000000..6615e4838ee4 --- /dev/null +++ b/arch/sh/kernel/entry.S @@ -0,0 +1,1149 @@ +/* $Id: entry.S,v 1.37 2004/06/11 13:02:46 doyu Exp $ + * + * linux/arch/sh/entry.S + * + * Copyright (C) 1999, 2000, 2002 Niibe Yutaka + * 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/sys.h> +#include <linux/linkage.h> +#include <linux/config.h> +#include <asm/asm-offsets.h> +#include <asm/thread_info.h> +#include <asm/unistd.h> + +#if !defined(CONFIG_NFSD) && !defined(CONFIG_NFSD_MODULE) +#define sys_nfsservctl sys_ni_syscall +#endif + +#if !defined(CONFIG_MMU) +#define sys_madvise sys_ni_syscall +#define sys_readahead sys_ni_syscall +#define sys_mprotect sys_ni_syscall +#define sys_msync sys_ni_syscall +#define sys_mlock sys_ni_syscall +#define sys_munlock sys_ni_syscall +#define sys_mlockall sys_ni_syscall +#define sys_munlockall sys_ni_syscall +#define sys_mremap sys_ni_syscall +#define sys_mincore sys_ni_syscall +#define sys_remap_file_pages sys_ni_syscall +#endif + +! NOTE: +! GNU as (as of 2.9.1) changes bf/s into bt/s and bra, when the address +! to be jumped is too far, but it causes illegal slot exception. + +/* + * entry.S contains the system-call and fault low-level handling routines. + * This also contains the timer-interrupt handler, as well as all interrupts + * and faults that can result in a task-switch. + * + * NOTE: This code handles signal-recognition, which happens every time + * after a timer-interrupt and after each system call. + * + * NOTE: This code uses a convention that instructions in the delay slot + * of a transfer-control instruction are indented by an extra space, thus: + * + * jmp @k0 ! control-transfer instruction + * ldc k1, ssr ! delay slot + * + * Stack layout in 'ret_from_syscall': + * ptrace needs to have all regs on the stack. + * if the order here is changed, it needs to be + * updated in ptrace.c and ptrace.h + * + * r0 + * ... + * r15 = stack pointer + * spc + * pr + * ssr + * gbr + * mach + * macl + * syscall # + * + */ + +ENOSYS = 38 +EINVAL = 22 + +#if defined(CONFIG_CPU_SH3) +TRA = 0xffffffd0 +EXPEVT = 0xffffffd4 +#if defined(CONFIG_CPU_SUBTYPE_SH7707) || defined(CONFIG_CPU_SUBTYPE_SH7709) || \ + defined(CONFIG_CPU_SUBTYPE_SH7300) || defined(CONFIG_CPU_SUBTYPE_SH7705) +INTEVT = 0xa4000000 ! INTEVTE2(0xa4000000) +#else +INTEVT = 0xffffffd8 +#endif +MMU_TEA = 0xfffffffc ! TLB Exception Address Register +#elif defined(CONFIG_CPU_SH4) +TRA = 0xff000020 +EXPEVT = 0xff000024 +INTEVT = 0xff000028 +MMU_TEA = 0xff00000c ! TLB Exception Address Register +#endif + +#if defined(CONFIG_KGDB_NMI) +NMI_VEC = 0x1c0 ! Must catch early for debounce +#endif + +/* Offsets to the stack */ +OFF_R0 = 0 /* Return value. New ABI also arg4 */ +OFF_R1 = 4 /* New ABI: arg5 */ +OFF_R2 = 8 /* New ABI: arg6 */ +OFF_R3 = 12 /* New ABI: syscall_nr */ +OFF_R4 = 16 /* New ABI: arg0 */ +OFF_R5 = 20 /* New ABI: arg1 */ +OFF_R6 = 24 /* New ABI: arg2 */ +OFF_R7 = 28 /* New ABI: arg3 */ +OFF_SP = (15*4) +OFF_PC = (16*4) +OFF_SR = (16*4+8) +OFF_TRA = (16*4+6*4) + + +#define k0 r0 +#define k1 r1 +#define k2 r2 +#define k3 r3 +#define k4 r4 + +#define k_ex_code r2_bank /* r2_bank1 */ +#define g_imask r6 /* r6_bank1 */ +#define k_g_imask r6_bank /* r6_bank1 */ +#define current r7 /* r7_bank1 */ + +/* + * Kernel mode register usage: + * k0 scratch + * k1 scratch + * k2 scratch (Exception code) + * k3 scratch (Return address) + * k4 scratch + * k5 reserved + * k6 Global Interrupt Mask (0--15 << 4) + * k7 CURRENT_THREAD_INFO (pointer to current thread info) + */ + +! +! TLB Miss / Initial Page write exception handling +! _and_ +! TLB hits, but the access violate the protection. +! It can be valid access, such as stack grow and/or C-O-W. +! +! +! Find the pmd/pte entry and loadtlb +! If it's not found, cause address error (SEGV) +! +! Although this could be written in assembly language (and it'd be faster), +! this first version depends *much* on C implementation. +! + +#define CLI() \ + stc sr, r0; \ + or #0xf0, r0; \ + ldc r0, sr + +#define STI() \ + mov.l __INV_IMASK, r11; \ + stc sr, r10; \ + and r11, r10; \ + stc k_g_imask, r11; \ + or r11, r10; \ + ldc r10, sr + +#if defined(CONFIG_PREEMPT) +# define preempt_stop() CLI() +#else +# define preempt_stop() +# define resume_kernel restore_all +#endif + +#if defined(CONFIG_MMU) + .align 2 +ENTRY(tlb_miss_load) + bra call_dpf + mov #0, r5 + + .align 2 +ENTRY(tlb_miss_store) + bra call_dpf + mov #1, r5 + + .align 2 +ENTRY(initial_page_write) + bra call_dpf + mov #1, r5 + + .align 2 +ENTRY(tlb_protection_violation_load) + bra call_dpf + mov #0, r5 + + .align 2 +ENTRY(tlb_protection_violation_store) + bra call_dpf + mov #1, r5 + +call_dpf: + mov.l 1f, r0 + mov r5, r8 + mov.l @r0, r6 + mov r6, r9 + mov.l 2f, r0 + sts pr, r10 + jsr @r0 + mov r15, r4 + ! + tst r0, r0 + bf/s 0f + lds r10, pr + rts + nop +0: STI() + mov.l 3f, r0 + mov r9, r6 + mov r8, r5 + jmp @r0 + mov r15, r4 + + .align 2 +1: .long MMU_TEA +2: .long __do_page_fault +3: .long do_page_fault + + .align 2 +ENTRY(address_error_load) + bra call_dae + mov #0,r5 ! writeaccess = 0 + + .align 2 +ENTRY(address_error_store) + bra call_dae + mov #1,r5 ! writeaccess = 1 + + .align 2 +call_dae: + mov.l 1f, r0 + mov.l @r0, r6 ! address + mov.l 2f, r0 + jmp @r0 + mov r15, r4 ! regs + + .align 2 +1: .long MMU_TEA +2: .long do_address_error +#endif /* CONFIG_MMU */ + +#if defined(CONFIG_SH_STANDARD_BIOS) || defined(CONFIG_SH_KGDB) +! Handle kernel debug if either kgdb (SW) or gdb-stub (FW) is present. +! If both are configured, handle the debug traps (breakpoints) in SW, +! but still allow BIOS traps to FW. + + .align 2 +debug_kernel: +#if defined(CONFIG_SH_STANDARD_BIOS) && defined(CONFIG_SH_KGDB) + /* Force BIOS call to FW (debug_trap put TRA in r8) */ + mov r8,r0 + shlr2 r0 + cmp/eq #0x3f,r0 + bt debug_kernel_fw +#endif /* CONFIG_SH_STANDARD_BIOS && CONFIG_SH_KGDB */ + +debug_enter: +#if defined(CONFIG_SH_KGDB) + /* Jump to kgdb, pass stacked regs as arg */ +debug_kernel_sw: + mov.l 3f, r0 + jmp @r0 + mov r15, r4 + .align 2 +3: .long kgdb_handle_exception +#endif /* CONFIG_SH_KGDB */ + +#if defined(CONFIG_SH_STANDARD_BIOS) + /* Unwind the stack and jmp to the debug entry */ +debug_kernel_fw: + mov.l @r15+, r0 + mov.l @r15+, r1 + mov.l @r15+, r2 + mov.l @r15+, r3 + mov.l @r15+, r4 + mov.l @r15+, r5 + mov.l @r15+, r6 + mov.l @r15+, r7 + stc sr, r8 + mov.l 1f, r9 ! BL =1, RB=1, IMASK=0x0F + or r9, r8 + ldc r8, sr ! here, change the register bank + mov.l @r15+, r8 + mov.l @r15+, r9 + mov.l @r15+, r10 + mov.l @r15+, r11 + mov.l @r15+, r12 + mov.l @r15+, r13 + mov.l @r15+, r14 + mov.l @r15+, k0 + ldc.l @r15+, spc + lds.l @r15+, pr + mov.l @r15+, k1 + ldc.l @r15+, gbr + lds.l @r15+, mach + lds.l @r15+, macl + mov k0, r15 + ! + mov.l 2f, k0 + mov.l @k0, k0 + jmp @k0 + ldc k1, ssr + .align 2 +1: .long 0x300000f0 +2: .long gdb_vbr_vector +#endif /* CONFIG_SH_STANDARD_BIOS */ + +#endif /* CONFIG_SH_STANDARD_BIOS || CONFIG_SH_KGDB */ + + + .align 2 +debug_trap: +#if defined(CONFIG_SH_STANDARD_BIOS) || defined(CONFIG_SH_KGDB) + mov #OFF_SR, r0 + mov.l @(r0,r15), r0 ! get status register + shll r0 + shll r0 ! kernel space? + bt/s debug_kernel +#endif + mov.l @r15, r0 ! Restore R0 value + mov.l 1f, r8 + jmp @r8 + nop + + .align 2 +ENTRY(exception_error) + ! + STI() + mov.l 2f, r0 + jmp @r0 + nop + +! + .align 2 +1: .long break_point_trap_software +2: .long do_exception_error + + .align 2 +ret_from_exception: + preempt_stop() +ret_from_irq: + ! + mov #OFF_SR, r0 + mov.l @(r0,r15), r0 ! get status register + shll r0 + shll r0 ! kernel space? + bt/s resume_kernel ! Yes, it's from kernel, go back soon + GET_THREAD_INFO(r8) + +#ifdef CONFIG_PREEMPT + bra resume_userspace + nop +ENTRY(resume_kernel) + mov.l @(TI_PRE_COUNT,r8), r0 ! current_thread_info->preempt_count + tst r0, r0 + bf noresched +need_resched: + mov.l @(TI_FLAGS,r8), r0 ! current_thread_info->flags + tst #_TIF_NEED_RESCHED, r0 ! need_resched set? + bt noresched + + mov #OFF_SR, r0 + mov.l @(r0,r15), r0 ! get status register + and #0xf0, r0 ! interrupts off (exception path)? + cmp/eq #0xf0, r0 + bt noresched + + mov.l 1f, r0 + mov.l r0, @(TI_PRE_COUNT,r8) + + STI() + mov.l 2f, r0 + jsr @r0 + nop + mov #0, r0 + mov.l r0, @(TI_PRE_COUNT,r8) + CLI() + + bra need_resched + nop +noresched: + bra restore_all + nop + + .align 2 +1: .long PREEMPT_ACTIVE +2: .long schedule +#endif + +ENTRY(resume_userspace) + ! r8: current_thread_info + CLI() + mov.l @(TI_FLAGS,r8), r0 ! current_thread_info->flags + tst #_TIF_WORK_MASK, r0 + bt/s restore_all + tst #_TIF_NEED_RESCHED, r0 + + .align 2 +work_pending: + ! r0: current_thread_info->flags + ! r8: current_thread_info + ! t: result of "tst #_TIF_NEED_RESCHED, r0" + bf/s work_resched + tst #_TIF_SIGPENDING, r0 +work_notifysig: + bt/s restore_all + mov r15, r4 + mov #0, r5 + mov.l 2f, r1 + mova restore_all, r0 + jmp @r1 + lds r0, pr +work_resched: +#ifndef CONFIG_PREEMPT + ! gUSA handling + mov.l @(OFF_SP,r15), r0 ! get user space stack pointer + mov r0, r1 + shll r0 + bf/s 1f + shll r0 + bf/s 1f + mov #OFF_PC, r0 + ! SP >= 0xc0000000 : gUSA mark + mov.l @(r0,r15), r2 ! get user space PC (program counter) + mov.l @(OFF_R0,r15), r3 ! end point + cmp/hs r3, r2 ! r2 >= r3? + bt 1f + add r3, r1 ! rewind point #2 + mov.l r1, @(r0,r15) ! reset PC to rewind point #2 + ! +1: +#endif + mov.l 1f, r1 + jsr @r1 ! schedule + nop + CLI() + ! + mov.l @(TI_FLAGS,r8), r0 ! current_thread_info->flags + tst #_TIF_WORK_MASK, r0 + bt restore_all + bra work_pending + tst #_TIF_NEED_RESCHED, r0 + + .align 2 +1: .long schedule +2: .long do_signal + + .align 2 +syscall_exit_work: + ! r0: current_thread_info->flags + ! r8: current_thread_info + tst #_TIF_SYSCALL_TRACE, r0 + bt/s work_pending + tst #_TIF_NEED_RESCHED, r0 + STI() + ! XXX setup arguments... + mov.l 4f, r0 ! do_syscall_trace + jsr @r0 + nop + bra resume_userspace + nop + + .align 2 +syscall_trace_entry: + ! Yes it is traced. + ! XXX setup arguments... + mov.l 4f, r11 ! Call do_syscall_trace which notifies + jsr @r11 ! superior (will chomp R[0-7]) + nop + ! Reload R0-R4 from kernel stack, where the + ! parent may have modified them using + ! ptrace(POKEUSR). (Note that R0-R2 are + ! used by the system call handler directly + ! from the kernel stack anyway, so don't need + ! to be reloaded here.) This allows the parent + ! to rewrite system calls and args on the fly. + mov.l @(OFF_R4,r15), r4 ! arg0 + mov.l @(OFF_R5,r15), r5 + mov.l @(OFF_R6,r15), r6 + mov.l @(OFF_R7,r15), r7 ! arg3 + mov.l @(OFF_R3,r15), r3 ! syscall_nr + ! Arrange for do_syscall_trace to be called + ! again as the system call returns. + mov.l 2f, r10 ! Number of syscalls + cmp/hs r10, r3 + bf syscall_call + mov #-ENOSYS, r0 + bra syscall_exit + mov.l r0, @(OFF_R0,r15) ! Return value + +/* + * Syscall interface: + * + * Syscall #: R3 + * Arguments #0 to #3: R4--R7 + * Arguments #4 to #6: R0, R1, R2 + * TRA: (number of arguments + 0x10) x 4 + * + * This code also handles delegating other traps to the BIOS/gdb stub + * according to: + * + * Trap number + * (TRA>>2) Purpose + * -------- ------- + * 0x0-0xf old syscall ABI + * 0x10-0x1f new syscall ABI + * 0x20-0xff delegated through debug_trap to BIOS/gdb stub. + * + * Note: When we're first called, the TRA value must be shifted + * right 2 bits in order to get the value that was used as the "trapa" + * argument. + */ + + .align 2 + .globl ret_from_fork +ret_from_fork: + mov.l 1f, r8 + jsr @r8 + mov r0, r4 + bra syscall_exit + nop + .align 2 +1: .long schedule_tail + ! +ENTRY(system_call) + mov.l 1f, r9 + mov.l @r9, r8 ! Read from TRA (Trap Address) Register + ! + ! Is the trap argument >= 0x20? (TRA will be >= 0x80) + mov #0x7f, r9 + cmp/hi r9, r8 + bt/s 0f + mov #OFF_TRA, r9 + add r15, r9 + ! + mov.l r8, @r9 ! set TRA value to tra + STI() + ! Call the system call handler through the table. + ! First check for bad syscall number + mov r3, r9 + mov.l 2f, r8 ! Number of syscalls + cmp/hs r8, r9 + bf/s good_system_call + GET_THREAD_INFO(r8) +syscall_badsys: ! Bad syscall number + mov #-ENOSYS, r0 + bra resume_userspace + mov.l r0, @(OFF_R0,r15) ! Return value + ! +0: + bra debug_trap + nop + ! +good_system_call: ! Good syscall number + mov.l @(TI_FLAGS,r8), r8 + mov #_TIF_SYSCALL_TRACE, r10 + tst r10, r8 + bf syscall_trace_entry + ! +syscall_call: + shll2 r9 ! x4 + mov.l 3f, r8 ! Load the address of sys_call_table + add r8, r9 + mov.l @r9, r8 + jsr @r8 ! jump to specific syscall handler + nop + mov.l r0, @(OFF_R0,r15) ! save the return value + ! +syscall_exit: + CLI() + ! + GET_THREAD_INFO(r8) + mov.l @(TI_FLAGS,r8), r0 ! current_thread_info->flags + tst #_TIF_ALLWORK_MASK, r0 + bf syscall_exit_work +restore_all: + mov.l @r15+, r0 + mov.l @r15+, r1 + mov.l @r15+, r2 + mov.l @r15+, r3 + mov.l @r15+, r4 + mov.l @r15+, r5 + mov.l @r15+, r6 + mov.l @r15+, r7 + ! + stc sr, r8 + mov.l 7f, r9 + or r9, r8 ! BL =1, RB=1 + ldc r8, sr ! here, change the register bank + ! + mov.l @r15+, r8 + mov.l @r15+, r9 + mov.l @r15+, r10 + mov.l @r15+, r11 + mov.l @r15+, r12 + mov.l @r15+, r13 + mov.l @r15+, r14 + mov.l @r15+, k4 ! original stack pointer + ldc.l @r15+, spc + lds.l @r15+, pr + mov.l @r15+, k3 ! original SR + ldc.l @r15+, gbr + lds.l @r15+, mach + lds.l @r15+, macl + add #4, r15 ! Skip syscall number + ! +#ifdef CONFIG_SH_DSP + mov.l @r15+, k0 ! DSP mode marker + mov.l 5f, k1 + cmp/eq k0, k1 ! Do we have a DSP stack frame? + bf skip_restore + + stc sr, k0 ! Enable CPU DSP mode + or k1, k0 ! (within kernel it may be disabled) + ldc k0, sr + mov r2, k0 ! Backup r2 + + ! Restore DSP registers from stack + mov r15, r2 + movs.l @r2+, a1 + movs.l @r2+, a0g + movs.l @r2+, a1g + movs.l @r2+, m0 + movs.l @r2+, m1 + mov r2, r15 + + lds.l @r15+, a0 + lds.l @r15+, x0 + lds.l @r15+, x1 + lds.l @r15+, y0 + lds.l @r15+, y1 + lds.l @r15+, dsr + ldc.l @r15+, rs + ldc.l @r15+, re + ldc.l @r15+, mod + + mov k0, r2 ! Restore r2 +skip_restore: +#endif + ! + ! Calculate new SR value + mov k3, k2 ! original SR value + mov.l 9f, k1 + and k1, k2 ! Mask orignal SR value + ! + mov k3, k0 ! Calculate IMASK-bits + shlr2 k0 + and #0x3c, k0 + cmp/eq #0x3c, k0 + bt/s 6f + shll2 k0 + mov g_imask, k0 + ! +6: or k0, k2 ! Set the IMASK-bits + ldc k2, ssr + ! +#if defined(CONFIG_KGDB_NMI) + ! Clear in_nmi + mov.l 4f, k0 + mov #0, k1 + mov.b k1, @k0 +#endif + mov.l @r15+, k2 ! restore EXPEVT + mov k4, r15 + rte + nop + + .align 2 +1: .long TRA +2: .long NR_syscalls +3: .long sys_call_table +4: .long do_syscall_trace +5: .long 0x00001000 ! DSP +7: .long 0x30000000 +9: +__INV_IMASK: + .long 0xffffff0f ! ~(IMASK) + +! Exception Vector Base +! +! Should be aligned page boundary. +! + .balign 4096,0,4096 +ENTRY(vbr_base) + .long 0 +! + .balign 256,0,256 +general_exception: + mov.l 1f, k2 + mov.l 2f, k3 + bra handle_exception + mov.l @k2, k2 + .align 2 +1: .long EXPEVT +2: .long ret_from_exception +! +! + .balign 1024,0,1024 +tlb_miss: + mov.l 1f, k2 + mov.l 4f, k3 + bra handle_exception + mov.l @k2, k2 +! + .balign 512,0,512 +interrupt: + mov.l 2f, k2 + mov.l 3f, k3 +#if defined(CONFIG_KGDB_NMI) + ! Debounce (filter nested NMI) + mov.l @k2, k0 + mov.l 5f, k1 + cmp/eq k1, k0 + bf 0f + mov.l 6f, k1 + tas.b @k1 + bt 0f + rte + nop + .align 2 +5: .long NMI_VEC +6: .long in_nmi +0: +#endif /* defined(CONFIG_KGDB_NMI) */ + bra handle_exception + mov.l @k2, k2 + + .align 2 +1: .long EXPEVT +2: .long INTEVT +3: .long ret_from_irq +4: .long ret_from_exception + +! +! + .align 2 +handle_exception: + ! Using k0, k1 for scratch registers (r0_bank1, r1_bank), + ! save all registers onto stack. + ! + stc ssr, k0 ! Is it from kernel space? + shll k0 ! Check MD bit (bit30) by shifting it into... + shll k0 ! ...the T bit + bt/s 1f ! It's a kernel to kernel transition. + mov r15, k0 ! save original stack to k0 + /* User space to kernel */ + mov #0x20, k1 + shll8 k1 ! k1 := 8192 (== THREAD_SIZE) + add current, k1 + mov k1, r15 ! change to kernel stack + ! +1: mov #-1, k4 + mov.l 2f, k1 + ! +#ifdef CONFIG_SH_DSP + mov.l r2, @-r15 ! Save r2, we need another reg + stc sr, k4 + mov.l 1f, r2 + tst r2, k4 ! Check if in DSP mode + mov.l @r15+, r2 ! Restore r2 now + bt/s skip_save + mov #0, k4 ! Set marker for no stack frame + + mov r2, k4 ! Backup r2 (in k4) for later + + ! Save DSP registers on stack + stc.l mod, @-r15 + stc.l re, @-r15 + stc.l rs, @-r15 + sts.l dsr, @-r15 + sts.l y1, @-r15 + sts.l y0, @-r15 + sts.l x1, @-r15 + sts.l x0, @-r15 + sts.l a0, @-r15 + + ! GAS is broken, does not generate correct "movs.l Ds,@-As" instr. + + ! FIXME: Make sure that this is still the case with newer toolchains, + ! as we're not at all interested in supporting ancient toolchains at + ! this point. -- PFM. + + mov r15, r2 + .word 0xf653 ! movs.l a1, @-r2 + .word 0xf6f3 ! movs.l a0g, @-r2 + .word 0xf6d3 ! movs.l a1g, @-r2 + .word 0xf6c3 ! movs.l m0, @-r2 + .word 0xf6e3 ! movs.l m1, @-r2 + mov r2, r15 + + mov k4, r2 ! Restore r2 + mov.l 1f, k4 ! Force DSP stack frame +skip_save: + mov.l k4, @-r15 ! Push DSP mode marker onto stack +#endif + ! Save the user registers on the stack. + mov.l k2, @-r15 ! EXPEVT + mov.l k4, @-r15 ! set TRA (default: -1) + ! + sts.l macl, @-r15 + sts.l mach, @-r15 + stc.l gbr, @-r15 + stc.l ssr, @-r15 + sts.l pr, @-r15 + stc.l spc, @-r15 + ! + lds k3, pr ! Set the return address to pr + ! + mov.l k0, @-r15 ! save orignal stack + mov.l r14, @-r15 + mov.l r13, @-r15 + mov.l r12, @-r15 + mov.l r11, @-r15 + mov.l r10, @-r15 + mov.l r9, @-r15 + mov.l r8, @-r15 + ! + stc sr, r8 ! Back to normal register bank, and + or k1, r8 ! Block all interrupts + mov.l 3f, k1 + and k1, r8 ! ... + ldc r8, sr ! ...changed here. + ! + mov.l r7, @-r15 + mov.l r6, @-r15 + mov.l r5, @-r15 + mov.l r4, @-r15 + mov.l r3, @-r15 + mov.l r2, @-r15 + mov.l r1, @-r15 + mov.l r0, @-r15 + ! Then, dispatch to the handler, according to the exception code. + stc k_ex_code, r8 + shlr2 r8 + shlr r8 + mov.l 4f, r9 + add r8, r9 + mov.l @r9, r9 + jmp @r9 + nop + + .align 2 +1: .long 0x00001000 ! DSP=1 +2: .long 0x000080f0 ! FD=1, IMASK=15 +3: .long 0xcfffffff ! RB=0, BL=0 +4: .long exception_handling_table + + .align 2 +ENTRY(exception_none) + rts + nop + + .data +ENTRY(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 + .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 + .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_pread_wrapper /* 180 */ + .long sys_pwrite_wrapper + .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 + .long sys_getdents64 /* 220 */ + .long sys_fcntl64 + .long sys_ni_syscall /* reserved for TUX */ + .long sys_ni_syscall /* Reserved for Security */ + .long sys_gettid + .long sys_readahead /* 225 */ + .long sys_setxattr + .long sys_lsetxattr + .long sys_fsetxattr + .long sys_getxattr + .long sys_lgetxattr /* 230 */ + .long sys_fgetxattr + .long sys_listxattr + .long sys_llistxattr + .long sys_flistxattr + .long sys_removexattr /* 235 */ + .long sys_lremovexattr + .long sys_fremovexattr + .long sys_tkill + .long sys_sendfile64 + .long sys_futex /* 240 */ + .long sys_sched_setaffinity + .long sys_sched_getaffinity + .long sys_ni_syscall + .long sys_ni_syscall + .long sys_io_setup /* 245 */ + .long sys_io_destroy + .long sys_io_getevents + .long sys_io_submit + .long sys_io_cancel + .long sys_fadvise64 /* 250 */ + .long sys_ni_syscall + .long sys_exit_group + .long sys_lookup_dcookie + .long sys_epoll_create + .long sys_epoll_ctl /* 255 */ + .long sys_epoll_wait + .long sys_remap_file_pages + .long sys_set_tid_address + .long sys_timer_create + .long sys_timer_settime /* 260 */ + .long sys_timer_gettime + .long sys_timer_getoverrun + .long sys_timer_delete + .long sys_clock_settime + .long sys_clock_gettime /* 265 */ + .long sys_clock_getres + .long sys_clock_nanosleep + .long sys_statfs64 + .long sys_fstatfs64 + .long sys_tgkill /* 270 */ + .long sys_utimes + .long sys_fadvise64_64_wrapper + .long sys_ni_syscall /* Reserved for vserver */ + .long sys_ni_syscall /* Reserved for mbind */ + .long sys_ni_syscall /* 275 - get_mempolicy */ + .long sys_ni_syscall /* set_mempolicy */ + .long sys_mq_open + .long sys_mq_unlink + .long sys_mq_timedsend + .long sys_mq_timedreceive /* 280 */ + .long sys_mq_notify + .long sys_mq_getsetattr + .long sys_ni_syscall /* Reserved for kexec */ + .long sys_waitid + .long sys_add_key /* 285 */ + .long sys_request_key + .long sys_keyctl + +/* End of entry.S */ diff --git a/arch/sh/kernel/head.S b/arch/sh/kernel/head.S new file mode 100644 index 000000000000..9b9e6ef626ce --- /dev/null +++ b/arch/sh/kernel/head.S @@ -0,0 +1,76 @@ +/* $Id: head.S,v 1.7 2003/09/01 17:58:19 lethal Exp $ + * + * arch/sh/kernel/head.S + * + * Copyright (C) 1999, 2000 Niibe Yutaka & Kaz Kojima + * + * 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. + * + * Head.S contains the SH exception handlers and startup code. + */ +#include <linux/linkage.h> + + .section .empty_zero_page, "aw" +ENTRY(empty_zero_page) + .long 1 /* MOUNT_ROOT_RDONLY */ + .long 0 /* RAMDISK_FLAGS */ + .long 0x0200 /* ORIG_ROOT_DEV */ + .long 1 /* LOADER_TYPE */ + .long 0x00360000 /* INITRD_START */ + .long 0x000a0000 /* INITRD_SIZE */ + .long 0 + .balign 4096,0,4096 + + .text +/* + * Condition at the entry of _stext: + * + * BSC has already been initialized. + * INTC may or may not be initialized. + * VBR may or may not be initialized. + * MMU may or may not be initialized. + * Cache may or may not be initialized. + * Hardware (including on-chip modules) may or may not be initialized. + * + */ +ENTRY(_stext) + ! Initialize Status Register + mov.l 1f, r0 ! MD=1, RB=0, BL=0, IMASK=0xF + ldc r0, sr + ! Initialize global interrupt mask + mov #0, r0 + ldc r0, r6_bank + ! + mov.l 2f, r0 + mov r0, r15 ! Set initial r15 (stack pointer) + mov #0x20, r1 ! + shll8 r1 ! r1 = 8192 + sub r1, r0 ! + ldc r0, r7_bank ! ... and initial thread_info + ! + ! Additional CPU initialization + mov.l 6f, r0 + jsr @r0 + nop + ! Clear BSS area + mov.l 3f, r1 + add #4, r1 + mov.l 4f, r2 + mov #0, r0 +9: cmp/hs r2, r1 + bf/s 9b ! while (r1 < r2) + mov.l r0,@-r2 + ! Start kernel + mov.l 5f, r0 + jmp @r0 + nop + + .balign 4 +1: .long 0x400080F0 ! MD=1, RB=0, BL=0, FD=1, IMASK=0xF +2: .long stack +3: .long __bss_start +4: .long _end +5: .long start_kernel +6: .long sh_cpu_init diff --git a/arch/sh/kernel/init_task.c b/arch/sh/kernel/init_task.c new file mode 100644 index 000000000000..44053ea92936 --- /dev/null +++ b/arch/sh/kernel/init_task.c @@ -0,0 +1,36 @@ +#include <linux/mm.h> +#include <linux/module.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); + +EXPORT_SYMBOL(init_mm); + +/* + * Initial thread structure. + * + * We need to make sure that this is 8192-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); + +EXPORT_SYMBOL(init_task); diff --git a/arch/sh/kernel/io.c b/arch/sh/kernel/io.c new file mode 100644 index 000000000000..d9932f25993b --- /dev/null +++ b/arch/sh/kernel/io.c @@ -0,0 +1,59 @@ +/* + * linux/arch/sh/kernel/io.c + * + * Copyright (C) 2000 Stuart Menefy + * + * Provide real functions which expand to whatever the header file defined. + * Also definitions of machine independent IO functions. + */ + +#include <asm/io.h> +#include <linux/module.h> + +/* + * Copy data from IO memory space to "real" memory space. + * This needs to be optimized. + */ +void memcpy_fromio(void * to, unsigned long from, unsigned long count) +{ + char *p = to; + while (count) { + count--; + *p = readb(from); + p++; + from++; + } +} + +/* + * Copy data from "real" memory space to IO memory space. + * This needs to be optimized. + */ +void memcpy_toio(unsigned long to, const void * from, unsigned long count) +{ + const char *p = from; + while (count) { + count--; + writeb(*p, to); + p++; + to++; + } +} + +/* + * "memset" on IO memory space. + * This needs to be optimized. + */ +void memset_io(unsigned long dst, int c, unsigned long count) +{ + while (count) { + count--; + writeb(c, dst); + dst++; + } +} + +EXPORT_SYMBOL(memcpy_fromio); +EXPORT_SYMBOL(memcpy_toio); +EXPORT_SYMBOL(memset_io); + diff --git a/arch/sh/kernel/io_generic.c b/arch/sh/kernel/io_generic.c new file mode 100644 index 000000000000..a911b0149d1f --- /dev/null +++ b/arch/sh/kernel/io_generic.c @@ -0,0 +1,243 @@ +/* $Id: io_generic.c,v 1.2 2003/05/04 19:29:53 lethal Exp $ + * + * linux/arch/sh/kernel/io_generic.c + * + * Copyright (C) 2000 Niibe Yutaka + * + * Generic I/O routine. These can be used where a machine specific version + * is not required. + * + * 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 <asm/io.h> +#include <asm/machvec.h> +#include <linux/module.h> + +#if defined(CONFIG_CPU_SH3) +/* I'm not sure SH7709 has this kind of bug */ +#define SH3_PCMCIA_BUG_WORKAROUND 1 +#define DUMMY_READ_AREA6 0xba000000 +#endif + +#define PORT2ADDR(x) (sh_mv.mv_isa_port2addr(x)) + +unsigned long generic_io_base; + +static inline void delay(void) +{ + ctrl_inw(0xa0000000); +} + +unsigned char generic_inb(unsigned long port) +{ + return *(volatile unsigned char*)PORT2ADDR(port); +} + +unsigned short generic_inw(unsigned long port) +{ + return *(volatile unsigned short*)PORT2ADDR(port); +} + +unsigned int generic_inl(unsigned long port) +{ + return *(volatile unsigned long*)PORT2ADDR(port); +} + +unsigned char generic_inb_p(unsigned long port) +{ + unsigned long v = *(volatile unsigned char*)PORT2ADDR(port); + + delay(); + return v; +} + +unsigned short generic_inw_p(unsigned long port) +{ + unsigned long v = *(volatile unsigned short*)PORT2ADDR(port); + + delay(); + return v; +} + +unsigned int generic_inl_p(unsigned long port) +{ + unsigned long v = *(volatile unsigned long*)PORT2ADDR(port); + + delay(); + return v; +} + +/* + * insb/w/l all read a series of bytes/words/longs from a fixed port + * address. However as the port address doesn't change we only need to + * convert the port address to real address once. + */ + +void generic_insb(unsigned long port, void *buffer, unsigned long count) +{ + volatile unsigned char *port_addr; + unsigned char *buf=buffer; + + port_addr = (volatile unsigned char *)PORT2ADDR(port); + + while(count--) + *buf++ = *port_addr; +} + +void generic_insw(unsigned long port, void *buffer, unsigned long count) +{ + volatile unsigned short *port_addr; + unsigned short *buf=buffer; + + port_addr = (volatile unsigned short *)PORT2ADDR(port); + + while(count--) + *buf++ = *port_addr; +#ifdef SH3_PCMCIA_BUG_WORKAROUND + ctrl_inb (DUMMY_READ_AREA6); +#endif +} + +void generic_insl(unsigned long port, void *buffer, unsigned long count) +{ + volatile unsigned long *port_addr; + unsigned long *buf=buffer; + + port_addr = (volatile unsigned long *)PORT2ADDR(port); + + while(count--) + *buf++ = *port_addr; +#ifdef SH3_PCMCIA_BUG_WORKAROUND + ctrl_inb (DUMMY_READ_AREA6); +#endif +} + +void generic_outb(unsigned char b, unsigned long port) +{ + *(volatile unsigned char*)PORT2ADDR(port) = b; +} + +void generic_outw(unsigned short b, unsigned long port) +{ + *(volatile unsigned short*)PORT2ADDR(port) = b; +} + +void generic_outl(unsigned int b, unsigned long port) +{ + *(volatile unsigned long*)PORT2ADDR(port) = b; +} + +void generic_outb_p(unsigned char b, unsigned long port) +{ + *(volatile unsigned char*)PORT2ADDR(port) = b; + delay(); +} + +void generic_outw_p(unsigned short b, unsigned long port) +{ + *(volatile unsigned short*)PORT2ADDR(port) = b; + delay(); +} + +void generic_outl_p(unsigned int b, unsigned long port) +{ + *(volatile unsigned long*)PORT2ADDR(port) = b; + delay(); +} + +/* + * outsb/w/l all write a series of bytes/words/longs to a fixed port + * address. However as the port address doesn't change we only need to + * convert the port address to real address once. + */ + +void generic_outsb(unsigned long port, const void *buffer, unsigned long count) +{ + volatile unsigned char *port_addr; + const unsigned char *buf=buffer; + + port_addr = (volatile unsigned char *)PORT2ADDR(port); + + while(count--) + *port_addr = *buf++; +} + +void generic_outsw(unsigned long port, const void *buffer, unsigned long count) +{ + volatile unsigned short *port_addr; + const unsigned short *buf=buffer; + + port_addr = (volatile unsigned short *)PORT2ADDR(port); + + while(count--) + *port_addr = *buf++; + +#ifdef SH3_PCMCIA_BUG_WORKAROUND + ctrl_inb (DUMMY_READ_AREA6); +#endif +} + +void generic_outsl(unsigned long port, const void *buffer, unsigned long count) +{ + volatile unsigned long *port_addr; + const unsigned long *buf=buffer; + + port_addr = (volatile unsigned long *)PORT2ADDR(port); + + while(count--) + *port_addr = *buf++; + +#ifdef SH3_PCMCIA_BUG_WORKAROUND + ctrl_inb (DUMMY_READ_AREA6); +#endif +} + +unsigned char generic_readb(unsigned long addr) +{ + return *(volatile unsigned char*)addr; +} + +unsigned short generic_readw(unsigned long addr) +{ + return *(volatile unsigned short*)addr; +} + +unsigned int generic_readl(unsigned long addr) +{ + return *(volatile unsigned long*)addr; +} + +void generic_writeb(unsigned char b, unsigned long addr) +{ + *(volatile unsigned char*)addr = b; +} + +void generic_writew(unsigned short b, unsigned long addr) +{ + *(volatile unsigned short*)addr = b; +} + +void generic_writel(unsigned int b, unsigned long addr) +{ + *(volatile unsigned long*)addr = b; +} + +void * generic_ioremap(unsigned long offset, unsigned long size) +{ + return (void *) P2SEGADDR(offset); +} +EXPORT_SYMBOL(generic_ioremap); + +void generic_iounmap(void *addr) +{ +} +EXPORT_SYMBOL(generic_iounmap); + +unsigned long generic_isa_port2addr(unsigned long offset) +{ + return offset + generic_io_base; +} diff --git a/arch/sh/kernel/irq.c b/arch/sh/kernel/irq.c new file mode 100644 index 000000000000..54c171225b78 --- /dev/null +++ b/arch/sh/kernel/irq.c @@ -0,0 +1,106 @@ +/* $Id: irq.c,v 1.20 2004/01/13 05:52:11 kkojima Exp $ + * + * linux/arch/sh/kernel/irq.c + * + * Copyright (C) 1992, 1998 Linus Torvalds, Ingo Molnar + * + * + * SuperH version: Copyright (C) 1999 Niibe Yutaka + */ + +/* + * 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/module.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/kernel_stat.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/timex.h> +#include <linux/mm.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/kallsyms.h> +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/pgalloc.h> +#include <asm/delay.h> +#include <asm/irq.h> +#include <linux/irq.h> + + +/* + * 'what should we do if we get a hw irq event on an illegal vector'. + * each architecture has to answer this themselves, it doesn't deserve + * a generic callback i think. + */ +void ack_bad_irq(unsigned int irq) +{ + printk("unexpected IRQ trap at vector %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 < ACTUAL_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 + +asmlinkage int do_IRQ(unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + int irq; + + irq_enter(); + asm volatile("stc r2_bank, %0\n\t" + "shlr2 %0\n\t" + "shlr2 %0\n\t" + "shlr %0\n\t" + "add #-16, %0\n\t" + :"=z" (irq)); + irq = irq_demux(irq); + __do_IRQ(irq, ®s); + irq_exit(); + return 1; +} diff --git a/arch/sh/kernel/kgdb_jmp.S b/arch/sh/kernel/kgdb_jmp.S new file mode 100644 index 000000000000..339bb1d7ff0b --- /dev/null +++ b/arch/sh/kernel/kgdb_jmp.S @@ -0,0 +1,33 @@ +#include <linux/linkage.h> + +ENTRY(setjmp) + add #(9*4), r4 + sts.l pr, @-r4 + mov.l r15, @-r4 + mov.l r14, @-r4 + mov.l r13, @-r4 + mov.l r12, @-r4 + mov.l r11, @-r4 + mov.l r10, @-r4 + mov.l r9, @-r4 + mov.l r8, @-r4 + rts + mov #0, r0 + +ENTRY(longjmp) + mov.l @r4+, r8 + mov.l @r4+, r9 + mov.l @r4+, r10 + mov.l @r4+, r11 + mov.l @r4+, r12 + mov.l @r4+, r13 + mov.l @r4+, r14 + mov.l @r4+, r15 + lds.l @r4+, pr + mov r5, r0 + tst r0, r0 + bf 1f + mov #1, r0 ! in case val==0 +1: rts + nop + diff --git a/arch/sh/kernel/kgdb_stub.c b/arch/sh/kernel/kgdb_stub.c new file mode 100644 index 000000000000..42638b92b51c --- /dev/null +++ b/arch/sh/kernel/kgdb_stub.c @@ -0,0 +1,1491 @@ +/* + * May be copied or modified under the terms of the GNU General Public + * License. See linux/COPYING for more information. + * + * Containes extracts from code by Glenn Engel, Jim Kingdon, + * David Grothe <dave@gcom.com>, Tigran Aivazian <tigran@sco.com>, + * Amit S. Kale <akale@veritas.com>, William Gatliff <bgat@open-widgets.com>, + * Ben Lee, Steve Chamberlain and Benoit Miller <fulg@iname.com>. + * + * This version by Henry Bell <henry.bell@st.com> + * Minor modifications by Jeremy Siegel <jsiegel@mvista.com> + * + * Contains low-level support for remote debug using GDB. + * + * To enable debugger support, two things need to happen. A call to + * set_debug_traps() is necessary in order to allow any breakpoints + * or error conditions to be properly intercepted and reported to gdb. + * A breakpoint also needs to be generated to begin communication. This + * is most easily accomplished by a call to breakpoint() which does + * a trapa if the initialisation phase has been successfully completed. + * + * In this case, set_debug_traps() is not used to "take over" exceptions; + * other kernel code is modified instead to enter the kgdb functions here + * when appropriate (see entry.S for breakpoint traps and NMI interrupts, + * see traps.c for kernel error exceptions). + * + * The following gdb commands are supported: + * + * Command Function Return value + * + * g return the value of the CPU registers hex data or ENN + * G set the value of the CPU registers OK or ENN + * + * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN + * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN + * XAA..AA,LLLL: Same, but data is binary (not hex) OK or ENN + * + * c Resume at current address SNN ( signal NN) + * cAA..AA Continue at address AA..AA SNN + * CNN; Resume at current address with signal SNN + * CNN;AA..AA Resume at address AA..AA with signal SNN + * + * s Step one instruction SNN + * sAA..AA Step one instruction from AA..AA SNN + * SNN; Step one instruction with signal SNN + * SNNAA..AA Step one instruction from AA..AA w/NN SNN + * + * k kill (Detach GDB) + * + * d Toggle debug flag + * D Detach GDB + * + * Hct Set thread t for operations, OK or ENN + * c = 'c' (step, cont), c = 'g' (other + * operations) + * + * qC Query current thread ID QCpid + * qfThreadInfo Get list of current threads (first) m<id> + * qsThreadInfo " " " " " (subsequent) + * qOffsets Get section offsets Text=x;Data=y;Bss=z + * + * TXX Find if thread XX is alive OK or ENN + * ? What was the last sigval ? SNN (signal NN) + * O Output to GDB console + * + * Remote communication protocol. + * + * A debug packet whose contents are <data> is encapsulated for + * transmission in the form: + * + * $ <data> # CSUM1 CSUM2 + * + * <data> must be ASCII alphanumeric and cannot include characters + * '$' or '#'. If <data> starts with two characters followed by + * ':', then the existing stubs interpret this as a sequence number. + * + * CSUM1 and CSUM2 are ascii hex representation of an 8-bit + * checksum of <data>, the most significant nibble is sent first. + * the hex digits 0-9,a-f are used. + * + * Receiver responds with: + * + * + - if CSUM is correct and ready for next packet + * - - if CSUM is incorrect + * + * Responses can be run-length encoded to save space. A '*' means that + * the next character is an ASCII encoding giving a repeat count which + * stands for that many repititions of the character preceding the '*'. + * The encoding is n+29, yielding a printable character where n >=3 + * (which is where RLE starts to win). Don't use an n > 126. + * + * So "0* " means the same as "0000". + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/linkage.h> +#include <linux/init.h> + +#include <asm/system.h> +#include <asm/current.h> +#include <asm/signal.h> +#include <asm/pgtable.h> +#include <asm/ptrace.h> +#include <asm/kgdb.h> + +#ifdef CONFIG_SH_KGDB_CONSOLE +#include <linux/console.h> +#endif + +/* Function pointers for linkage */ +kgdb_debug_hook_t *kgdb_debug_hook; +kgdb_bus_error_hook_t *kgdb_bus_err_hook; + +int (*kgdb_getchar)(void); +void (*kgdb_putchar)(int); + +static void put_debug_char(int c) +{ + if (!kgdb_putchar) + return; + (*kgdb_putchar)(c); +} +static int get_debug_char(void) +{ + if (!kgdb_getchar) + return -1; + return (*kgdb_getchar)(); +} + +/* Num chars in in/out bound buffers, register packets need NUMREGBYTES * 2 */ +#define BUFMAX 1024 +#define NUMREGBYTES (MAXREG*4) +#define OUTBUFMAX (NUMREGBYTES*2+512) + +enum regs { + R0 = 0, R1, R2, R3, R4, R5, R6, R7, + R8, R9, R10, R11, R12, R13, R14, R15, + PC, PR, GBR, VBR, MACH, MACL, SR, + /* */ + MAXREG +}; + +static unsigned int registers[MAXREG]; +struct kgdb_regs trap_registers; + +char kgdb_in_gdb_mode; +char in_nmi; /* Set during NMI to prevent reentry */ +int kgdb_nofault; /* Boolean to ignore bus errs (i.e. in GDB) */ +int kgdb_enabled = 1; /* Default to enabled, cmdline can disable */ +int kgdb_halt; + +/* Exposed for user access */ +struct task_struct *kgdb_current; +unsigned int kgdb_g_imask; +int kgdb_trapa_val; +int kgdb_excode; + +/* Default values for SCI (can override via kernel args in setup.c) */ +#ifndef CONFIG_KGDB_DEFPORT +#define CONFIG_KGDB_DEFPORT 1 +#endif + +#ifndef CONFIG_KGDB_DEFBAUD +#define CONFIG_KGDB_DEFBAUD 115200 +#endif + +#if defined(CONFIG_KGDB_DEFPARITY_E) +#define CONFIG_KGDB_DEFPARITY 'E' +#elif defined(CONFIG_KGDB_DEFPARITY_O) +#define CONFIG_KGDB_DEFPARITY 'O' +#else /* CONFIG_KGDB_DEFPARITY_N */ +#define CONFIG_KGDB_DEFPARITY 'N' +#endif + +#ifdef CONFIG_KGDB_DEFBITS_7 +#define CONFIG_KGDB_DEFBITS '7' +#else /* CONFIG_KGDB_DEFBITS_8 */ +#define CONFIG_KGDB_DEFBITS '8' +#endif + +/* SCI/UART settings, used in kgdb_console_setup() */ +int kgdb_portnum = CONFIG_KGDB_DEFPORT; +int kgdb_baud = CONFIG_KGDB_DEFBAUD; +char kgdb_parity = CONFIG_KGDB_DEFPARITY; +char kgdb_bits = CONFIG_KGDB_DEFBITS; + +/* Jump buffer for setjmp/longjmp */ +static jmp_buf rem_com_env; + +/* TRA differs sh3/4 */ +#if defined(CONFIG_CPU_SH3) +#define TRA 0xffffffd0 +#elif defined(CONFIG_CPU_SH4) +#define TRA 0xff000020 +#endif + +/* Macros for single step instruction identification */ +#define OPCODE_BT(op) (((op) & 0xff00) == 0x8900) +#define OPCODE_BF(op) (((op) & 0xff00) == 0x8b00) +#define OPCODE_BTF_DISP(op) (((op) & 0x80) ? (((op) | 0xffffff80) << 1) : \ + (((op) & 0x7f ) << 1)) +#define OPCODE_BFS(op) (((op) & 0xff00) == 0x8f00) +#define OPCODE_BTS(op) (((op) & 0xff00) == 0x8d00) +#define OPCODE_BRA(op) (((op) & 0xf000) == 0xa000) +#define OPCODE_BRA_DISP(op) (((op) & 0x800) ? (((op) | 0xfffff800) << 1) : \ + (((op) & 0x7ff) << 1)) +#define OPCODE_BRAF(op) (((op) & 0xf0ff) == 0x0023) +#define OPCODE_BRAF_REG(op) (((op) & 0x0f00) >> 8) +#define OPCODE_BSR(op) (((op) & 0xf000) == 0xb000) +#define OPCODE_BSR_DISP(op) (((op) & 0x800) ? (((op) | 0xfffff800) << 1) : \ + (((op) & 0x7ff) << 1)) +#define OPCODE_BSRF(op) (((op) & 0xf0ff) == 0x0003) +#define OPCODE_BSRF_REG(op) (((op) >> 8) & 0xf) +#define OPCODE_JMP(op) (((op) & 0xf0ff) == 0x402b) +#define OPCODE_JMP_REG(op) (((op) >> 8) & 0xf) +#define OPCODE_JSR(op) (((op) & 0xf0ff) == 0x400b) +#define OPCODE_JSR_REG(op) (((op) >> 8) & 0xf) +#define OPCODE_RTS(op) ((op) == 0xb) +#define OPCODE_RTE(op) ((op) == 0x2b) + +#define SR_T_BIT_MASK 0x1 +#define STEP_OPCODE 0xc320 +#define BIOS_CALL_TRAP 0x3f + +/* Exception codes as per SH-4 core manual */ +#define ADDRESS_ERROR_LOAD_VEC 7 +#define ADDRESS_ERROR_STORE_VEC 8 +#define TRAP_VEC 11 +#define INVALID_INSN_VEC 12 +#define INVALID_SLOT_VEC 13 +#define NMI_VEC 14 +#define USER_BREAK_VEC 15 +#define SERIAL_BREAK_VEC 58 + +/* Misc static */ +static int stepped_address; +static short stepped_opcode; +static const char hexchars[] = "0123456789abcdef"; +static char in_buffer[BUFMAX]; +static char out_buffer[OUTBUFMAX]; + +static void kgdb_to_gdb(const char *s); + +#ifdef CONFIG_KGDB_THREAD +static struct task_struct *trapped_thread; +static struct task_struct *current_thread; +typedef unsigned char threadref[8]; +#define BUF_THREAD_ID_SIZE 16 +#endif + +/* Return addr as a real volatile address */ +static inline unsigned int ctrl_inl(const unsigned long addr) +{ + return *(volatile unsigned long *) addr; +} + +/* Correctly set *addr using volatile */ +static inline void ctrl_outl(const unsigned int b, unsigned long addr) +{ + *(volatile unsigned long *) addr = b; +} + +/* Get high hex bits */ +static char highhex(const int x) +{ + return hexchars[(x >> 4) & 0xf]; +} + +/* Get low hex bits */ +static char lowhex(const int x) +{ + return hexchars[x & 0xf]; +} + +/* Convert ch to hex */ +static int hex(const char ch) +{ + if ((ch >= 'a') && (ch <= 'f')) + return (ch - 'a' + 10); + if ((ch >= '0') && (ch <= '9')) + return (ch - '0'); + if ((ch >= 'A') && (ch <= 'F')) + return (ch - 'A' + 10); + return (-1); +} + +/* Convert the memory pointed to by mem into hex, placing result in buf. + Returns a pointer to the last char put in buf (null) */ +static char *mem_to_hex(const char *mem, char *buf, const int count) +{ + int i; + int ch; + unsigned short s_val; + unsigned long l_val; + + /* Check for 16 or 32 */ + if (count == 2 && ((long) mem & 1) == 0) { + s_val = *(unsigned short *) mem; + mem = (char *) &s_val; + } else if (count == 4 && ((long) mem & 3) == 0) { + l_val = *(unsigned long *) mem; + mem = (char *) &l_val; + } + for (i = 0; i < count; i++) { + ch = *mem++; + *buf++ = highhex(ch); + *buf++ = lowhex(ch); + } + *buf = 0; + return (buf); +} + +/* Convert the hex array pointed to by buf into binary, to be placed in mem. + Return a pointer to the character after the last byte written */ +static char *hex_to_mem(const char *buf, char *mem, const int count) +{ + int i; + unsigned char ch; + + for (i = 0; i < count; i++) { + ch = hex(*buf++) << 4; + ch = ch + hex(*buf++); + *mem++ = ch; + } + return (mem); +} + +/* While finding valid hex chars, convert to an integer, then return it */ +static int hex_to_int(char **ptr, int *int_value) +{ + int num_chars = 0; + int hex_value; + + *int_value = 0; + + while (**ptr) { + hex_value = hex(**ptr); + if (hex_value >= 0) { + *int_value = (*int_value << 4) | hex_value; + num_chars++; + } else + break; + (*ptr)++; + } + return num_chars; +} + +/* Copy the binary array pointed to by buf into mem. Fix $, #, + and 0x7d escaped with 0x7d. Return a pointer to the character + after the last byte written. */ +static char *ebin_to_mem(const char *buf, char *mem, int count) +{ + for (; count > 0; count--, buf++) { + if (*buf == 0x7d) + *mem++ = *(++buf) ^ 0x20; + else + *mem++ = *buf; + } + return mem; +} + +/* Pack a hex byte */ +static char *pack_hex_byte(char *pkt, int byte) +{ + *pkt++ = hexchars[(byte >> 4) & 0xf]; + *pkt++ = hexchars[(byte & 0xf)]; + return pkt; +} + +#ifdef CONFIG_KGDB_THREAD + +/* Pack a thread ID */ +static char *pack_threadid(char *pkt, threadref * id) +{ + char *limit; + unsigned char *altid; + + altid = (unsigned char *) id; + + limit = pkt + BUF_THREAD_ID_SIZE; + while (pkt < limit) + pkt = pack_hex_byte(pkt, *altid++); + return pkt; +} + +/* Convert an integer into our threadref */ +static void int_to_threadref(threadref * id, const int value) +{ + unsigned char *scan = (unsigned char *) id; + int i = 4; + + while (i--) + *scan++ = 0; + + *scan++ = (value >> 24) & 0xff; + *scan++ = (value >> 16) & 0xff; + *scan++ = (value >> 8) & 0xff; + *scan++ = (value & 0xff); +} + +/* Return a task structure ptr for a particular pid */ +static struct task_struct *get_thread(int pid) +{ + struct task_struct *thread; + + /* Use PID_MAX w/gdb for pid 0 */ + if (pid == PID_MAX) pid = 0; + + /* First check via PID */ + thread = find_task_by_pid(pid); + + if (thread) + return thread; + + /* Start at the start */ + thread = init_tasks[0]; + + /* Walk along the linked list of tasks */ + do { + if (thread->pid == pid) + return thread; + thread = thread->next_task; + } while (thread != init_tasks[0]); + + return NULL; +} + +#endif /* CONFIG_KGDB_THREAD */ + +/* Scan for the start char '$', read the packet and check the checksum */ +static void get_packet(char *buffer, int buflen) +{ + unsigned char checksum; + unsigned char xmitcsum; + int i; + int count; + char ch; + + do { + /* Ignore everything until the start character */ + while ((ch = get_debug_char()) != '$'); + + checksum = 0; + xmitcsum = -1; + count = 0; + + /* Now, read until a # or end of buffer is found */ + while (count < (buflen - 1)) { + ch = get_debug_char(); + + if (ch == '#') + break; + + checksum = checksum + ch; + buffer[count] = ch; + count = count + 1; + } + + buffer[count] = 0; + + /* Continue to read checksum following # */ + if (ch == '#') { + xmitcsum = hex(get_debug_char()) << 4; + xmitcsum += hex(get_debug_char()); + + /* Checksum */ + if (checksum != xmitcsum) + put_debug_char('-'); /* Failed checksum */ + else { + /* Ack successful transfer */ + put_debug_char('+'); + + /* If a sequence char is present, reply + the sequence ID */ + if (buffer[2] == ':') { + put_debug_char(buffer[0]); + put_debug_char(buffer[1]); + + /* Remove sequence chars from buffer */ + count = strlen(buffer); + for (i = 3; i <= count; i++) + buffer[i - 3] = buffer[i]; + } + } + } + } + while (checksum != xmitcsum); /* Keep trying while we fail */ +} + +/* Send the packet in the buffer with run-length encoding */ +static void put_packet(char *buffer) +{ + int checksum; + char *src; + int runlen; + int encode; + + do { + src = buffer; + put_debug_char('$'); + checksum = 0; + + /* Continue while we still have chars left */ + while (*src) { + /* Check for runs up to 99 chars long */ + for (runlen = 1; runlen < 99; runlen++) { + if (src[0] != src[runlen]) + break; + } + + if (runlen > 3) { + /* Got a useful amount, send encoding */ + encode = runlen + ' ' - 4; + put_debug_char(*src); checksum += *src; + put_debug_char('*'); checksum += '*'; + put_debug_char(encode); checksum += encode; + src += runlen; + } else { + /* Otherwise just send the current char */ + put_debug_char(*src); checksum += *src; + src += 1; + } + } + + /* '#' Separator, put high and low components of checksum */ + put_debug_char('#'); + put_debug_char(highhex(checksum)); + put_debug_char(lowhex(checksum)); + } + while ((get_debug_char()) != '+'); /* While no ack */ +} + +/* A bus error has occurred - perform a longjmp to return execution and + allow handling of the error */ +static void kgdb_handle_bus_error(void) +{ + longjmp(rem_com_env, 1); +} + +/* Translate SH-3/4 exception numbers to unix-like signal values */ +static int compute_signal(const int excep_code) +{ + int sigval; + + switch (excep_code) { + + case INVALID_INSN_VEC: + case INVALID_SLOT_VEC: + sigval = SIGILL; + break; + case ADDRESS_ERROR_LOAD_VEC: + case ADDRESS_ERROR_STORE_VEC: + sigval = SIGSEGV; + break; + + case SERIAL_BREAK_VEC: + case NMI_VEC: + sigval = SIGINT; + break; + + case USER_BREAK_VEC: + case TRAP_VEC: + sigval = SIGTRAP; + break; + + default: + sigval = SIGBUS; /* "software generated" */ + break; + } + + return (sigval); +} + +/* Make a local copy of the registers passed into the handler (bletch) */ +static void kgdb_regs_to_gdb_regs(const struct kgdb_regs *regs, + int *gdb_regs) +{ + gdb_regs[R0] = regs->regs[R0]; + gdb_regs[R1] = regs->regs[R1]; + gdb_regs[R2] = regs->regs[R2]; + gdb_regs[R3] = regs->regs[R3]; + gdb_regs[R4] = regs->regs[R4]; + gdb_regs[R5] = regs->regs[R5]; + gdb_regs[R6] = regs->regs[R6]; + gdb_regs[R7] = regs->regs[R7]; + gdb_regs[R8] = regs->regs[R8]; + gdb_regs[R9] = regs->regs[R9]; + gdb_regs[R10] = regs->regs[R10]; + gdb_regs[R11] = regs->regs[R11]; + gdb_regs[R12] = regs->regs[R12]; + gdb_regs[R13] = regs->regs[R13]; + gdb_regs[R14] = regs->regs[R14]; + gdb_regs[R15] = regs->regs[R15]; + gdb_regs[PC] = regs->pc; + gdb_regs[PR] = regs->pr; + gdb_regs[GBR] = regs->gbr; + gdb_regs[MACH] = regs->mach; + gdb_regs[MACL] = regs->macl; + gdb_regs[SR] = regs->sr; + gdb_regs[VBR] = regs->vbr; +} + +/* Copy local gdb registers back to kgdb regs, for later copy to kernel */ +static void gdb_regs_to_kgdb_regs(const int *gdb_regs, + struct kgdb_regs *regs) +{ + regs->regs[R0] = gdb_regs[R0]; + regs->regs[R1] = gdb_regs[R1]; + regs->regs[R2] = gdb_regs[R2]; + regs->regs[R3] = gdb_regs[R3]; + regs->regs[R4] = gdb_regs[R4]; + regs->regs[R5] = gdb_regs[R5]; + regs->regs[R6] = gdb_regs[R6]; + regs->regs[R7] = gdb_regs[R7]; + regs->regs[R8] = gdb_regs[R8]; + regs->regs[R9] = gdb_regs[R9]; + regs->regs[R10] = gdb_regs[R10]; + regs->regs[R11] = gdb_regs[R11]; + regs->regs[R12] = gdb_regs[R12]; + regs->regs[R13] = gdb_regs[R13]; + regs->regs[R14] = gdb_regs[R14]; + regs->regs[R15] = gdb_regs[R15]; + regs->pc = gdb_regs[PC]; + regs->pr = gdb_regs[PR]; + regs->gbr = gdb_regs[GBR]; + regs->mach = gdb_regs[MACH]; + regs->macl = gdb_regs[MACL]; + regs->sr = gdb_regs[SR]; + regs->vbr = gdb_regs[VBR]; +} + +#ifdef CONFIG_KGDB_THREAD +/* Make a local copy of registers from the specified thread */ +asmlinkage void ret_from_fork(void); +static void thread_regs_to_gdb_regs(const struct task_struct *thread, + int *gdb_regs) +{ + int regno; + int *tregs; + + /* Initialize to zero */ + for (regno = 0; regno < MAXREG; regno++) + gdb_regs[regno] = 0; + + /* Just making sure... */ + if (thread == NULL) + return; + + /* A new fork has pt_regs on the stack from a fork() call */ + if (thread->thread.pc == (unsigned long)ret_from_fork) { + + int vbr_val; + struct pt_regs *kregs; + kregs = (struct pt_regs*)thread->thread.sp; + + gdb_regs[R0] = kregs->regs[R0]; + gdb_regs[R1] = kregs->regs[R1]; + gdb_regs[R2] = kregs->regs[R2]; + gdb_regs[R3] = kregs->regs[R3]; + gdb_regs[R4] = kregs->regs[R4]; + gdb_regs[R5] = kregs->regs[R5]; + gdb_regs[R6] = kregs->regs[R6]; + gdb_regs[R7] = kregs->regs[R7]; + gdb_regs[R8] = kregs->regs[R8]; + gdb_regs[R9] = kregs->regs[R9]; + gdb_regs[R10] = kregs->regs[R10]; + gdb_regs[R11] = kregs->regs[R11]; + gdb_regs[R12] = kregs->regs[R12]; + gdb_regs[R13] = kregs->regs[R13]; + gdb_regs[R14] = kregs->regs[R14]; + gdb_regs[R15] = kregs->regs[R15]; + gdb_regs[PC] = kregs->pc; + gdb_regs[PR] = kregs->pr; + gdb_regs[GBR] = kregs->gbr; + gdb_regs[MACH] = kregs->mach; + gdb_regs[MACL] = kregs->macl; + gdb_regs[SR] = kregs->sr; + + asm("stc vbr, %0":"=r"(vbr_val)); + gdb_regs[VBR] = vbr_val; + return; + } + + /* Otherwise, we have only some registers from switch_to() */ + tregs = (int *)thread->thread.sp; + gdb_regs[R15] = (int)tregs; + gdb_regs[R14] = *tregs++; + gdb_regs[R13] = *tregs++; + gdb_regs[R12] = *tregs++; + gdb_regs[R11] = *tregs++; + gdb_regs[R10] = *tregs++; + gdb_regs[R9] = *tregs++; + gdb_regs[R8] = *tregs++; + gdb_regs[PR] = *tregs++; + gdb_regs[GBR] = *tregs++; + gdb_regs[PC] = thread->thread.pc; +} +#endif /* CONFIG_KGDB_THREAD */ + +/* Calculate the new address for after a step */ +static short *get_step_address(void) +{ + short op = *(short *) trap_registers.pc; + long addr; + + /* BT */ + if (OPCODE_BT(op)) { + if (trap_registers.sr & SR_T_BIT_MASK) + addr = trap_registers.pc + 4 + OPCODE_BTF_DISP(op); + else + addr = trap_registers.pc + 2; + } + + /* BTS */ + else if (OPCODE_BTS(op)) { + if (trap_registers.sr & SR_T_BIT_MASK) + addr = trap_registers.pc + 4 + OPCODE_BTF_DISP(op); + else + addr = trap_registers.pc + 4; /* Not in delay slot */ + } + + /* BF */ + else if (OPCODE_BF(op)) { + if (!(trap_registers.sr & SR_T_BIT_MASK)) + addr = trap_registers.pc + 4 + OPCODE_BTF_DISP(op); + else + addr = trap_registers.pc + 2; + } + + /* BFS */ + else if (OPCODE_BFS(op)) { + if (!(trap_registers.sr & SR_T_BIT_MASK)) + addr = trap_registers.pc + 4 + OPCODE_BTF_DISP(op); + else + addr = trap_registers.pc + 4; /* Not in delay slot */ + } + + /* BRA */ + else if (OPCODE_BRA(op)) + addr = trap_registers.pc + 4 + OPCODE_BRA_DISP(op); + + /* BRAF */ + else if (OPCODE_BRAF(op)) + addr = trap_registers.pc + 4 + + trap_registers.regs[OPCODE_BRAF_REG(op)]; + + /* BSR */ + else if (OPCODE_BSR(op)) + addr = trap_registers.pc + 4 + OPCODE_BSR_DISP(op); + + /* BSRF */ + else if (OPCODE_BSRF(op)) + addr = trap_registers.pc + 4 + + trap_registers.regs[OPCODE_BSRF_REG(op)]; + + /* JMP */ + else if (OPCODE_JMP(op)) + addr = trap_registers.regs[OPCODE_JMP_REG(op)]; + + /* JSR */ + else if (OPCODE_JSR(op)) + addr = trap_registers.regs[OPCODE_JSR_REG(op)]; + + /* RTS */ + else if (OPCODE_RTS(op)) + addr = trap_registers.pr; + + /* RTE */ + else if (OPCODE_RTE(op)) + addr = trap_registers.regs[15]; + + /* Other */ + else + addr = trap_registers.pc + 2; + + kgdb_flush_icache_range(addr, addr + 2); + return (short *) addr; +} + +/* Set up a single-step. Replace the instruction immediately after the + current instruction (i.e. next in the expected flow of control) with a + trap instruction, so that returning will cause only a single instruction + to be executed. Note that this model is slightly broken for instructions + with delay slots (e.g. B[TF]S, BSR, BRA etc), where both the branch + and the instruction in the delay slot will be executed. */ +static void do_single_step(void) +{ + unsigned short *addr = 0; + + /* Determine where the target instruction will send us to */ + addr = get_step_address(); + stepped_address = (int)addr; + + /* Replace it */ + stepped_opcode = *(short *)addr; + *addr = STEP_OPCODE; + + /* Flush and return */ + kgdb_flush_icache_range((long) addr, (long) addr + 2); + return; +} + +/* Undo a single step */ +static void undo_single_step(void) +{ + /* If we have stepped, put back the old instruction */ + /* Use stepped_address in case we stopped elsewhere */ + if (stepped_opcode != 0) { + *(short*)stepped_address = stepped_opcode; + kgdb_flush_icache_range(stepped_address, stepped_address + 2); + } + stepped_opcode = 0; +} + +/* Send a signal message */ +static void send_signal_msg(const int signum) +{ +#ifndef CONFIG_KGDB_THREAD + out_buffer[0] = 'S'; + out_buffer[1] = highhex(signum); + out_buffer[2] = lowhex(signum); + out_buffer[3] = 0; + put_packet(out_buffer); +#else /* CONFIG_KGDB_THREAD */ + int threadid; + threadref thref; + char *out = out_buffer; + const char *tstring = "thread"; + + *out++ = 'T'; + *out++ = highhex(signum); + *out++ = lowhex(signum); + + while (*tstring) { + *out++ = *tstring++; + } + *out++ = ':'; + + threadid = trapped_thread->pid; + if (threadid == 0) threadid = PID_MAX; + int_to_threadref(&thref, threadid); + pack_threadid(out, &thref); + out += BUF_THREAD_ID_SIZE; + *out++ = ';'; + + *out = 0; + put_packet(out_buffer); +#endif /* CONFIG_KGDB_THREAD */ +} + +/* Reply that all was well */ +static void send_ok_msg(void) +{ + strcpy(out_buffer, "OK"); + put_packet(out_buffer); +} + +/* Reply that an error occurred */ +static void send_err_msg(void) +{ + strcpy(out_buffer, "E01"); + put_packet(out_buffer); +} + +/* Empty message indicates unrecognised command */ +static void send_empty_msg(void) +{ + put_packet(""); +} + +/* Read memory due to 'm' message */ +static void read_mem_msg(void) +{ + char *ptr; + int addr; + int length; + + /* Jmp, disable bus error handler */ + if (setjmp(rem_com_env) == 0) { + + kgdb_nofault = 1; + + /* Walk through, have m<addr>,<length> */ + ptr = &in_buffer[1]; + if (hex_to_int(&ptr, &addr) && (*ptr++ == ',')) + if (hex_to_int(&ptr, &length)) { + ptr = 0; + if (length * 2 > OUTBUFMAX) + length = OUTBUFMAX / 2; + mem_to_hex((char *) addr, out_buffer, length); + } + if (ptr) + send_err_msg(); + else + put_packet(out_buffer); + } else + send_err_msg(); + + /* Restore bus error handler */ + kgdb_nofault = 0; +} + +/* Write memory due to 'M' or 'X' message */ +static void write_mem_msg(int binary) +{ + char *ptr; + int addr; + int length; + + if (setjmp(rem_com_env) == 0) { + + kgdb_nofault = 1; + + /* Walk through, have M<addr>,<length>:<data> */ + ptr = &in_buffer[1]; + if (hex_to_int(&ptr, &addr) && (*ptr++ == ',')) + if (hex_to_int(&ptr, &length) && (*ptr++ == ':')) { + if (binary) + ebin_to_mem(ptr, (char*)addr, length); + else + hex_to_mem(ptr, (char*)addr, length); + kgdb_flush_icache_range(addr, addr + length); + ptr = 0; + send_ok_msg(); + } + if (ptr) + send_err_msg(); + } else + send_err_msg(); + + /* Restore bus error handler */ + kgdb_nofault = 0; +} + +/* Continue message */ +static void continue_msg(void) +{ + /* Try to read optional parameter, PC unchanged if none */ + char *ptr = &in_buffer[1]; + int addr; + + if (hex_to_int(&ptr, &addr)) + trap_registers.pc = addr; +} + +/* Continue message with signal */ +static void continue_with_sig_msg(void) +{ + int signal; + char *ptr = &in_buffer[1]; + int addr; + + /* Report limitation */ + kgdb_to_gdb("Cannot force signal in kgdb, continuing anyway.\n"); + + /* Signal */ + hex_to_int(&ptr, &signal); + if (*ptr == ';') + ptr++; + + /* Optional address */ + if (hex_to_int(&ptr, &addr)) + trap_registers.pc = addr; +} + +/* Step message */ +static void step_msg(void) +{ + continue_msg(); + do_single_step(); +} + +/* Step message with signal */ +static void step_with_sig_msg(void) +{ + continue_with_sig_msg(); + do_single_step(); +} + +/* Send register contents */ +static void send_regs_msg(void) +{ +#ifdef CONFIG_KGDB_THREAD + if (!current_thread) + kgdb_regs_to_gdb_regs(&trap_registers, registers); + else + thread_regs_to_gdb_regs(current_thread, registers); +#else + kgdb_regs_to_gdb_regs(&trap_registers, registers); +#endif + + mem_to_hex((char *) registers, out_buffer, NUMREGBYTES); + put_packet(out_buffer); +} + +/* Set register contents - currently can't set other thread's registers */ +static void set_regs_msg(void) +{ +#ifdef CONFIG_KGDB_THREAD + if (!current_thread) { +#endif + kgdb_regs_to_gdb_regs(&trap_registers, registers); + hex_to_mem(&in_buffer[1], (char *) registers, NUMREGBYTES); + gdb_regs_to_kgdb_regs(registers, &trap_registers); + send_ok_msg(); +#ifdef CONFIG_KGDB_THREAD + } else + send_err_msg(); +#endif +} + + +#ifdef CONFIG_KGDB_THREAD + +/* Set the status for a thread */ +void set_thread_msg(void) +{ + int threadid; + struct task_struct *thread = NULL; + char *ptr; + + switch (in_buffer[1]) { + + /* To select which thread for gG etc messages, i.e. supported */ + case 'g': + + ptr = &in_buffer[2]; + hex_to_int(&ptr, &threadid); + thread = get_thread(threadid); + + /* If we haven't found it */ + if (!thread) { + send_err_msg(); + break; + } + + /* Set current_thread (or not) */ + if (thread == trapped_thread) + current_thread = NULL; + else + current_thread = thread; + send_ok_msg(); + break; + + /* To select which thread for cCsS messages, i.e. unsupported */ + case 'c': + send_ok_msg(); + break; + + default: + send_empty_msg(); + break; + } +} + +/* Is a thread alive? */ +static void thread_status_msg(void) +{ + char *ptr; + int threadid; + struct task_struct *thread = NULL; + + ptr = &in_buffer[1]; + hex_to_int(&ptr, &threadid); + thread = get_thread(threadid); + if (thread) + send_ok_msg(); + else + send_err_msg(); +} +/* Send the current thread ID */ +static void thread_id_msg(void) +{ + int threadid; + threadref thref; + + out_buffer[0] = 'Q'; + out_buffer[1] = 'C'; + + if (current_thread) + threadid = current_thread->pid; + else if (trapped_thread) + threadid = trapped_thread->pid; + else /* Impossible, but just in case! */ + { + send_err_msg(); + return; + } + + /* Translate pid 0 to PID_MAX for gdb */ + if (threadid == 0) threadid = PID_MAX; + + int_to_threadref(&thref, threadid); + pack_threadid(out_buffer + 2, &thref); + out_buffer[2 + BUF_THREAD_ID_SIZE] = '\0'; + put_packet(out_buffer); +} + +/* Send thread info */ +static void thread_info_msg(void) +{ + struct task_struct *thread = NULL; + int threadid; + char *pos; + threadref thref; + + /* Start with 'm' */ + out_buffer[0] = 'm'; + pos = &out_buffer[1]; + + /* For all possible thread IDs - this will overrun if > 44 threads! */ + /* Start at 1 and include PID_MAX (since GDB won't use pid 0...) */ + for (threadid = 1; threadid <= PID_MAX; threadid++) { + + read_lock(&tasklist_lock); + thread = get_thread(threadid); + read_unlock(&tasklist_lock); + + /* If it's a valid thread */ + if (thread) { + int_to_threadref(&thref, threadid); + pack_threadid(pos, &thref); + pos += BUF_THREAD_ID_SIZE; + *pos++ = ','; + } + } + *--pos = 0; /* Lose final comma */ + put_packet(out_buffer); + +} + +/* Return printable info for gdb's 'info threads' command */ +static void thread_extra_info_msg(void) +{ + int threadid; + struct task_struct *thread = NULL; + char buffer[20], *ptr; + int i; + + /* Extract thread ID */ + ptr = &in_buffer[17]; + hex_to_int(&ptr, &threadid); + thread = get_thread(threadid); + + /* If we don't recognise it, say so */ + if (thread == NULL) + strcpy(buffer, "(unknown)"); + else + strcpy(buffer, thread->comm); + + /* Construct packet */ + for (i = 0, ptr = out_buffer; buffer[i]; i++) + ptr = pack_hex_byte(ptr, buffer[i]); + + if (thread->thread.pc == (unsigned long)ret_from_fork) { + strcpy(buffer, "<new fork>"); + for (i = 0; buffer[i]; i++) + ptr = pack_hex_byte(ptr, buffer[i]); + } + + *ptr = '\0'; + put_packet(out_buffer); +} + +/* Handle all qFooBarBaz messages - have to use an if statement as + opposed to a switch because q messages can have > 1 char id. */ +static void query_msg(void) +{ + const char *q_start = &in_buffer[1]; + + /* qC = return current thread ID */ + if (strncmp(q_start, "C", 1) == 0) + thread_id_msg(); + + /* qfThreadInfo = query all threads (first) */ + else if (strncmp(q_start, "fThreadInfo", 11) == 0) + thread_info_msg(); + + /* qsThreadInfo = query all threads (subsequent). We know we have sent + them all after the qfThreadInfo message, so there are no to send */ + else if (strncmp(q_start, "sThreadInfo", 11) == 0) + put_packet("l"); /* el = last */ + + /* qThreadExtraInfo = supply printable information per thread */ + else if (strncmp(q_start, "ThreadExtraInfo", 15) == 0) + thread_extra_info_msg(); + + /* Unsupported - empty message as per spec */ + else + send_empty_msg(); +} +#endif /* CONFIG_KGDB_THREAD */ + +/* + * Bring up the ports.. + */ +static int kgdb_serial_setup(void) +{ + extern int kgdb_console_setup(struct console *co, char *options); + struct console dummy; + + kgdb_console_setup(&dummy, 0); + + return 0; +} + +/* The command loop, read and act on requests */ +static void kgdb_command_loop(const int excep_code, const int trapa_value) +{ + int sigval; + + if (excep_code == NMI_VEC) { +#ifndef CONFIG_KGDB_NMI + KGDB_PRINTK("Ignoring unexpected NMI?\n"); + return; +#else /* CONFIG_KGDB_NMI */ + if (!kgdb_enabled) { + kgdb_enabled = 1; + kgdb_init(); + } +#endif /* CONFIG_KGDB_NMI */ + } + + /* Ignore if we're disabled */ + if (!kgdb_enabled) + return; + +#ifdef CONFIG_KGDB_THREAD + /* Until GDB specifies a thread */ + current_thread = NULL; + trapped_thread = current; +#endif + + /* Enter GDB mode (e.g. after detach) */ + if (!kgdb_in_gdb_mode) { + /* Do serial setup, notify user, issue preemptive ack */ + kgdb_serial_setup(); + KGDB_PRINTK("Waiting for GDB (on %s%d at %d baud)\n", + (kgdb_porttype ? kgdb_porttype->name : ""), + kgdb_portnum, kgdb_baud); + kgdb_in_gdb_mode = 1; + put_debug_char('+'); + } + + /* Reply to host that an exception has occurred */ + sigval = compute_signal(excep_code); + send_signal_msg(sigval); + + /* TRAP_VEC exception indicates a software trap inserted in place of + code by GDB so back up PC by one instruction, as this instruction + will later be replaced by its original one. Do NOT do this for + trap 0xff, since that indicates a compiled-in breakpoint which + will not be replaced (and we would retake the trap forever) */ + if ((excep_code == TRAP_VEC) && (trapa_value != (0xff << 2))) { + trap_registers.pc -= 2; + } + + /* Undo any stepping we may have done */ + undo_single_step(); + + while (1) { + + out_buffer[0] = 0; + get_packet(in_buffer, BUFMAX); + + /* Examine first char of buffer to see what we need to do */ + switch (in_buffer[0]) { + + case '?': /* Send which signal we've received */ + send_signal_msg(sigval); + break; + + case 'g': /* Return the values of the CPU registers */ + send_regs_msg(); + break; + + case 'G': /* Set the value of the CPU registers */ + set_regs_msg(); + break; + + case 'm': /* Read LLLL bytes address AA..AA */ + read_mem_msg(); + break; + + case 'M': /* Write LLLL bytes address AA..AA, ret OK */ + write_mem_msg(0); /* 0 = data in hex */ + break; + + case 'X': /* Write LLLL bytes esc bin address AA..AA */ + if (kgdb_bits == '8') + write_mem_msg(1); /* 1 = data in binary */ + else + send_empty_msg(); + break; + + case 'C': /* Continue, signum included, we ignore it */ + continue_with_sig_msg(); + return; + + case 'c': /* Continue at address AA..AA (optional) */ + continue_msg(); + return; + + case 'S': /* Step, signum included, we ignore it */ + step_with_sig_msg(); + return; + + case 's': /* Step one instruction from AA..AA */ + step_msg(); + return; + +#ifdef CONFIG_KGDB_THREAD + + case 'H': /* Task related */ + set_thread_msg(); + break; + + case 'T': /* Query thread status */ + thread_status_msg(); + break; + + case 'q': /* Handle query - currently thread-related */ + query_msg(); + break; +#endif + + case 'k': /* 'Kill the program' with a kernel ? */ + break; + + case 'D': /* Detach from program, send reply OK */ + kgdb_in_gdb_mode = 0; + send_ok_msg(); + get_debug_char(); + return; + + default: + send_empty_msg(); + break; + } + } +} + +/* There has been an exception, most likely a breakpoint. */ +void kgdb_handle_exception(struct pt_regs *regs) +{ + int excep_code, vbr_val; + int count; + int trapa_value = ctrl_inl(TRA); + + /* Copy kernel regs (from stack) */ + for (count = 0; count < 16; count++) + trap_registers.regs[count] = regs->regs[count]; + trap_registers.pc = regs->pc; + trap_registers.pr = regs->pr; + trap_registers.sr = regs->sr; + trap_registers.gbr = regs->gbr; + trap_registers.mach = regs->mach; + trap_registers.macl = regs->macl; + + asm("stc vbr, %0":"=r"(vbr_val)); + trap_registers.vbr = vbr_val; + + /* Get excode for command loop call, user access */ + asm("stc r2_bank, %0":"=r"(excep_code)); + kgdb_excode = excep_code; + + /* Other interesting environment items for reference */ + asm("stc r6_bank, %0":"=r"(kgdb_g_imask)); + kgdb_current = current; + kgdb_trapa_val = trapa_value; + + /* Act on the exception */ + kgdb_command_loop(excep_code >> 5, trapa_value); + + kgdb_current = NULL; + + /* Copy back the (maybe modified) registers */ + for (count = 0; count < 16; count++) + regs->regs[count] = trap_registers.regs[count]; + regs->pc = trap_registers.pc; + regs->pr = trap_registers.pr; + regs->sr = trap_registers.sr; + regs->gbr = trap_registers.gbr; + regs->mach = trap_registers.mach; + regs->macl = trap_registers.macl; + + vbr_val = trap_registers.vbr; + asm("ldc %0, vbr": :"r"(vbr_val)); + + return; +} + +/* Trigger a breakpoint by function */ +void breakpoint(void) +{ + if (!kgdb_enabled) { + kgdb_enabled = 1; + kgdb_init(); + } + BREAKPOINT(); +} + +/* Initialise the KGDB data structures and serial configuration */ +int kgdb_init(void) +{ + if (!kgdb_enabled) + return 1; + + in_nmi = 0; + kgdb_nofault = 0; + stepped_opcode = 0; + kgdb_in_gdb_mode = 0; + + if (kgdb_serial_setup() != 0) { + KGDB_PRINTK("serial setup error\n"); + return -1; + } + + /* Init ptr to exception handler */ + kgdb_debug_hook = kgdb_handle_exception; + kgdb_bus_err_hook = kgdb_handle_bus_error; + + /* Enter kgdb now if requested, or just report init done */ + if (kgdb_halt) { + kgdb_in_gdb_mode = 1; + put_debug_char('+'); + breakpoint(); + } + else + { + KGDB_PRINTK("stub is initialized.\n"); + } + + return 0; +} + +/* Make function available for "user messages"; console will use it too. */ + +char gdbmsgbuf[BUFMAX]; +#define MAXOUT ((BUFMAX-2)/2) + +static void kgdb_msg_write(const char *s, unsigned count) +{ + int i; + int wcount; + char *bufptr; + + /* 'O'utput */ + gdbmsgbuf[0] = 'O'; + + /* Fill and send buffers... */ + while (count > 0) { + bufptr = gdbmsgbuf + 1; + + /* Calculate how many this time */ + wcount = (count > MAXOUT) ? MAXOUT : count; + + /* Pack in hex chars */ + for (i = 0; i < wcount; i++) + bufptr = pack_hex_byte(bufptr, s[i]); + *bufptr = '\0'; + + /* Move up */ + s += wcount; + count -= wcount; + + /* Write packet */ + put_packet(gdbmsgbuf); + } +} + +static void kgdb_to_gdb(const char *s) +{ + kgdb_msg_write(s, strlen(s)); +} + +#ifdef CONFIG_SH_KGDB_CONSOLE +void kgdb_console_write(struct console *co, const char *s, unsigned count) +{ + /* Bail if we're not talking to GDB */ + if (!kgdb_in_gdb_mode) + return; + + kgdb_msg_write(s, count); +} +#endif diff --git a/arch/sh/kernel/module.c b/arch/sh/kernel/module.c new file mode 100644 index 000000000000..142a4e5b7ebc --- /dev/null +++ b/arch/sh/kernel/module.c @@ -0,0 +1,146 @@ +/* Kernel module help for SH. + + 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 +*/ +#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; +} + +#define COPY_UNALIGNED_WORD(sw, tw, align) \ +{ \ + void *__s = &(sw), *__t = &(tw); \ + unsigned short *__s2 = __s, *__t2 = __t; \ + unsigned char *__s1 = __s, *__t1 = __t; \ + switch ((align)) \ + { \ + case 0: \ + *(unsigned long *) __t = *(unsigned long *) __s; \ + break; \ + case 2: \ + *__t2++ = *__s2++; \ + *__t2 = *__s2; \ + break; \ + default: \ + *__t1++ = *__s1++; \ + *__t1++ = *__s1++; \ + *__t1++ = *__s1++; \ + *__t1 = *__s1; \ + break; \ + } \ +} + +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; + uint32_t value; + int align; + + 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; + + switch (ELF32_R_TYPE(rel[i].r_info)) { + case R_SH_DIR32: + COPY_UNALIGNED_WORD (*location, value, align); + value += relocation; + COPY_UNALIGNED_WORD (value, *location, align); + break; + case R_SH_REL32: + relocation = (relocation - (Elf32_Addr) location); + COPY_UNALIGNED_WORD (*location, value, align); + value += relocation; + COPY_UNALIGNED_WORD (value, *location, align); + 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/sh/kernel/process.c b/arch/sh/kernel/process.c new file mode 100644 index 000000000000..3d024590c24e --- /dev/null +++ b/arch/sh/kernel/process.c @@ -0,0 +1,531 @@ +/* $Id: process.c,v 1.28 2004/05/05 16:54:23 lethal Exp $ + * + * linux/arch/sh/kernel/process.c + * + * Copyright (C) 1995 Linus Torvalds + * + * SuperH version: Copyright (C) 1999, 2000 Niibe Yutaka & Kaz Kojima + */ + +/* + * This file handles the architecture-dependent parts of process handling.. + */ + +#include <linux/module.h> +#include <linux/unistd.h> +#include <linux/mm.h> +#include <linux/elfcore.h> +#include <linux/slab.h> +#include <linux/a.out.h> +#include <linux/ptrace.h> +#include <linux/platform.h> +#include <linux/kallsyms.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/mmu_context.h> +#include <asm/elf.h> +#if defined(CONFIG_SH_HS7751RVOIP) +#include <asm/hs7751rvoip/hs7751rvoip.h> +#elif defined(CONFIG_SH_RTS7751R2D) +#include <asm/rts7751r2d/rts7751r2d.h> +#endif + +static int hlt_counter=0; + +int ubc_usercnt = 0; + +#define HARD_IDLE_TIMEOUT (HZ / 3) + +void disable_hlt(void) +{ + hlt_counter++; +} + +EXPORT_SYMBOL(disable_hlt); + +void enable_hlt(void) +{ + hlt_counter--; +} + +EXPORT_SYMBOL(enable_hlt); + +void default_idle(void) +{ + /* endless idle loop with no priority at all */ + while (1) { + if (hlt_counter) { + while (1) + if (need_resched()) + break; + } else { + while (!need_resched()) + cpu_sleep(); + } + + schedule(); + } +} + +void cpu_idle(void) +{ + default_idle(); +} + +void machine_restart(char * __unused) +{ + /* SR.BL=1 and invoke address error to let CPU reset (manual reset) */ + asm volatile("ldc %0, sr\n\t" + "mov.l @%1, %0" : : "r" (0x10000000), "r" (0x80000001)); +} + +EXPORT_SYMBOL(machine_restart); + +void machine_halt(void) +{ +#if defined(CONFIG_SH_HS7751RVOIP) + unsigned short value; + + value = ctrl_inw(PA_OUTPORTR); + ctrl_outw((value & 0xffdf), PA_OUTPORTR); +#elif defined(CONFIG_SH_RTS7751R2D) + ctrl_outw(0x0001, PA_POWOFF); +#endif + while (1) + cpu_sleep(); +} + +EXPORT_SYMBOL(machine_halt); + +void machine_power_off(void) +{ +#if defined(CONFIG_SH_HS7751RVOIP) + unsigned short value; + + value = ctrl_inw(PA_OUTPORTR); + ctrl_outw((value & 0xffdf), PA_OUTPORTR); +#elif defined(CONFIG_SH_RTS7751R2D) + ctrl_outw(0x0001, PA_POWOFF); +#endif +} + +EXPORT_SYMBOL(machine_power_off); + +void show_regs(struct pt_regs * regs) +{ + printk("\n"); + printk("Pid : %d, Comm: %20s\n", current->pid, current->comm); + print_symbol("PC is at %s\n", regs->pc); + printk("PC : %08lx SP : %08lx SR : %08lx ", + regs->pc, regs->regs[15], regs->sr); +#ifdef CONFIG_MMU + printk("TEA : %08x ", ctrl_inl(MMU_TEA)); +#else + printk(" "); +#endif + printk("%s\n", print_tainted()); + + printk("R0 : %08lx R1 : %08lx R2 : %08lx R3 : %08lx\n", + regs->regs[0],regs->regs[1], + regs->regs[2],regs->regs[3]); + printk("R4 : %08lx R5 : %08lx R6 : %08lx R7 : %08lx\n", + regs->regs[4],regs->regs[5], + regs->regs[6],regs->regs[7]); + printk("R8 : %08lx R9 : %08lx R10 : %08lx R11 : %08lx\n", + regs->regs[8],regs->regs[9], + regs->regs[10],regs->regs[11]); + printk("R12 : %08lx R13 : %08lx R14 : %08lx\n", + regs->regs[12],regs->regs[13], + regs->regs[14]); + printk("MACH: %08lx MACL: %08lx GBR : %08lx PR : %08lx\n", + regs->mach, regs->macl, regs->gbr, regs->pr); + + /* + * If we're in kernel mode, dump the stack too.. + */ + if (!user_mode(regs)) { + extern void show_task(unsigned long *sp); + unsigned long sp = regs->regs[15]; + + show_task((unsigned long *)sp); + } +} + +/* + * Create a kernel thread + */ + +/* + * This is the mechanism for creating a new kernel thread. + * + */ +extern void kernel_thread_helper(void); +__asm__(".align 5\n" + "kernel_thread_helper:\n\t" + "jsr @r5\n\t" + " nop\n\t" + "mov.l 1f, r1\n\t" + "jsr @r1\n\t" + " mov r0, r4\n\t" + ".align 2\n\t" + "1:.long do_exit"); + +int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) +{ /* Don't use this in BL=1(cli). Or else, CPU resets! */ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + regs.regs[4] = (unsigned long) arg; + regs.regs[5] = (unsigned long) fn; + + regs.pc = (unsigned long) kernel_thread_helper; + regs.sr = (1 << 30); + + /* Ok, create the new process.. */ + return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); +} + +/* + * Free current thread data structures etc.. + */ +void exit_thread(void) +{ + if (current->thread.ubc_pc) { + current->thread.ubc_pc = 0; + ubc_usercnt -= 1; + } +} + +void flush_thread(void) +{ +#if defined(CONFIG_SH_FPU) + struct task_struct *tsk = current; + struct pt_regs *regs = (struct pt_regs *) + ((unsigned long)tsk->thread_info + + THREAD_SIZE - sizeof(struct pt_regs) + - sizeof(unsigned long)); + + /* Forget lazy FPU state */ + clear_fpu(tsk, regs); + clear_used_math(); +#endif +} + +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) +{ + int fpvalid = 0; + +#if defined(CONFIG_SH_FPU) + struct task_struct *tsk = current; + + fpvalid = !!tsk_used_math(tsk); + if (fpvalid) { + unlazy_fpu(tsk, regs); + memcpy(fpu, &tsk->thread.fpu.hard, sizeof(*fpu)); + } +#endif + + return fpvalid; +} + +/* + * Capture the user space registers if the task is not running (in user space) + */ +int dump_task_regs(struct task_struct *tsk, elf_gregset_t *regs) +{ + struct pt_regs ptregs; + + ptregs = *(struct pt_regs *) + ((unsigned long)tsk->thread_info + THREAD_SIZE + - sizeof(struct pt_regs) +#ifdef CONFIG_SH_DSP + - sizeof(struct pt_dspregs) +#endif + - sizeof(unsigned long)); + elf_core_copy_regs(regs, &ptregs); + + return 1; +} + +int +dump_task_fpu (struct task_struct *tsk, elf_fpregset_t *fpu) +{ + int fpvalid = 0; + +#if defined(CONFIG_SH_FPU) + fpvalid = !!tsk_used_math(tsk); + if (fpvalid) { + struct pt_regs *regs = (struct pt_regs *) + ((unsigned long)tsk->thread_info + + THREAD_SIZE - sizeof(struct pt_regs) + - sizeof(unsigned long)); + unlazy_fpu(tsk, regs); + memcpy(fpu, &tsk->thread.fpu.hard, sizeof(*fpu)); + } +#endif + + return fpvalid; +} + +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; +#if defined(CONFIG_SH_FPU) + struct task_struct *tsk = current; + + unlazy_fpu(tsk, regs); + p->thread.fpu = tsk->thread.fpu; + copy_to_stopped_child_used_math(p); +#endif + + childregs = ((struct pt_regs *) + (THREAD_SIZE + (unsigned long) p->thread_info) +#ifdef CONFIG_SH_DSP + - sizeof(struct pt_dspregs) +#endif + - sizeof(unsigned long)) - 1; + *childregs = *regs; + + if (user_mode(regs)) { + childregs->regs[15] = usp; + } else { + childregs->regs[15] = (unsigned long)p->thread_info + THREAD_SIZE; + } + if (clone_flags & CLONE_SETTLS) { + childregs->gbr = childregs->regs[0]; + } + childregs->regs[0] = 0; /* Set return value for child */ + + p->thread.sp = (unsigned long) childregs; + p->thread.pc = (unsigned long) ret_from_fork; + + p->thread.ubc_pc = 0; + + 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); +} + +/* Tracing by user break controller. */ +static void +ubc_set_tracing(int asid, unsigned long pc) +{ + ctrl_outl(pc, UBC_BARA); + + /* We don't have any ASID settings for the SH-2! */ + if (cpu_data->type != CPU_SH7604) + ctrl_outb(asid, UBC_BASRA); + + ctrl_outl(0, UBC_BAMRA); + + if (cpu_data->type == CPU_SH7729) { + ctrl_outw(BBR_INST | BBR_READ | BBR_CPU, UBC_BBRA); + ctrl_outl(BRCR_PCBA | BRCR_PCTE, UBC_BRCR); + } else { + ctrl_outw(BBR_INST | BBR_READ, UBC_BBRA); + ctrl_outw(BRCR_PCBA, UBC_BRCR); + } +} + +/* + * switch_to(x,y) should switch tasks from x to y. + * + */ +struct task_struct *__switch_to(struct task_struct *prev, struct task_struct *next) +{ +#if defined(CONFIG_SH_FPU) + struct pt_regs *regs = (struct pt_regs *) + ((unsigned long)prev->thread_info + + THREAD_SIZE - sizeof(struct pt_regs) + - sizeof(unsigned long)); + unlazy_fpu(prev, regs); +#endif + +#ifdef CONFIG_PREEMPT + { + unsigned long flags; + struct pt_regs *regs; + + local_irq_save(flags); + regs = (struct pt_regs *) + ((unsigned long)prev->thread_info + + THREAD_SIZE - sizeof(struct pt_regs) +#ifdef CONFIG_SH_DSP + - sizeof(struct pt_dspregs) +#endif + - sizeof(unsigned long)); + if (user_mode(regs) && regs->regs[15] >= 0xc0000000) { + int offset = (int)regs->regs[15]; + + /* Reset stack pointer: clear critical region mark */ + regs->regs[15] = regs->regs[1]; + if (regs->pc < regs->regs[0]) + /* Go to rewind point */ + regs->pc = regs->regs[0] + offset; + } + local_irq_restore(flags); + } +#endif + + /* + * Restore the kernel mode register + * k7 (r7_bank1) + */ + asm volatile("ldc %0, r7_bank" + : /* no output */ + : "r" (next->thread_info)); + +#ifdef CONFIG_MMU + /* If no tasks are using the UBC, we're done */ + if (ubc_usercnt == 0) + /* If no tasks are using the UBC, we're done */; + else if (next->thread.ubc_pc && next->mm) { + ubc_set_tracing(next->mm->context & MMU_CONTEXT_ASID_MASK, + next->thread.ubc_pc); + } else { + ctrl_outw(0, UBC_BBRA); + ctrl_outw(0, UBC_BBRB); + } +#endif + + return prev; +} + +asmlinkage int sys_fork(unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ +#ifdef CONFIG_MMU + return do_fork(SIGCHLD, regs.regs[15], ®s, 0, NULL, NULL); +#else + /* fork almost works, enough to trick you into looking elsewhere :-( */ + return -EINVAL; +#endif +} + +asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp, + unsigned long parent_tidptr, + unsigned long child_tidptr, + struct pt_regs regs) +{ + if (!newsp) + newsp = regs.regs[15]; + return do_fork(clone_flags, newsp, ®s, 0, + (int __user *)parent_tidptr, (int __user *)child_tidptr); +} + +/* + * 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 r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.regs[15], ®s, + 0, NULL, NULL); +} + +/* + * sys_execve() executes a new program. + */ +asmlinkage int sys_execve(char *ufilename, char **uargv, + char **uenvp, unsigned long r7, + struct pt_regs regs) +{ + int error; + char *filename; + + 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, + ®s); + if (error == 0) { + task_lock(current); + current->ptrace &= ~PT_DTRACE; + task_unlock(current); + } + putname(filename); +out: + return error; +} + +unsigned long get_wchan(struct task_struct *p) +{ + unsigned long schedule_frame; + 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); + if (in_sched_functions(pc)) { + schedule_frame = ((unsigned long *)(long)p->thread.sp)[1]; + return (unsigned long)((unsigned long *)schedule_frame)[1]; + } + return pc; +} + +asmlinkage void break_point_trap(unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + /* Clear tracing. */ + ctrl_outw(0, UBC_BBRA); + ctrl_outw(0, UBC_BBRB); + current->thread.ubc_pc = 0; + ubc_usercnt -= 1; + + force_sig(SIGTRAP, current); +} + +asmlinkage void break_point_trap_software(unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + regs.pc -= 2; + force_sig(SIGTRAP, current); +} diff --git a/arch/sh/kernel/ptrace.c b/arch/sh/kernel/ptrace.c new file mode 100644 index 000000000000..1b0dfb4d8ea4 --- /dev/null +++ b/arch/sh/kernel/ptrace.c @@ -0,0 +1,320 @@ +/* + * linux/arch/sh/kernel/ptrace.c + * + * Original x86 implementation: + * By Ross Biro 1/23/92 + * edited by Linus Torvalds + * + * SuperH version: Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka + * + */ + +#include <linux/config.h> +#include <linux/kernel.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 <linux/slab.h> +#include <linux/security.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> + +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + +/* + * This routine will get a word off of the process kernel stack. + */ +static inline int get_stack_long(struct task_struct *task, int offset) +{ + unsigned char *stack; + + stack = (unsigned char *) + task->thread_info + THREAD_SIZE - sizeof(struct pt_regs) +#ifdef CONFIG_SH_DSP + - sizeof(struct pt_dspregs) +#endif + - sizeof(unsigned long); + stack += offset; + return (*((int *)stack)); +} + +/* + * This routine will put a word on 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_info + THREAD_SIZE - sizeof(struct pt_regs) +#ifdef CONFIG_SH_DSP + - sizeof(struct pt_dspregs) +#endif + - sizeof(unsigned long); + stack += offset; + *(unsigned long *) stack = data; + return 0; +} + +/* + * 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.. */ +} + +asmlinkage int sys_ptrace(long request, long pid, long addr, long data) +{ + struct task_struct *child; + struct user * dummy = NULL; + int ret; + + lock_kernel(); + ret = -EPERM; + if (request == PTRACE_TRACEME) { + /* are we already being traced? */ + if (current->ptrace & PT_PTRACED) + goto out; + ret = security_ptrace(current->parent, current); + if (ret) + 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 || + addr > sizeof(struct user) - 3) + break; + + if (addr < sizeof(struct pt_regs)) + tmp = get_stack_long(child, addr); + else if (addr >= (long) &dummy->fpu && + addr < (long) &dummy->u_fpvalid) { + if (!tsk_used_math(child)) { + if (addr == (long)&dummy->fpu.fpscr) + tmp = FPSCR_INIT; + else + tmp = 0; + } else + tmp = ((long *)&child->thread.fpu) + [(addr - (long)&dummy->fpu) >> 2]; + } else if (addr == (long) &dummy->u_fpvalid) + tmp = !!tsk_used_math(child); + else + tmp = 0; + 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 */ + ret = -EIO; + if ((addr & 3) || addr < 0 || + addr > sizeof(struct user) - 3) + break; + + if (addr < sizeof(struct pt_regs)) + ret = put_stack_long(child, addr, data); + else if (addr >= (long) &dummy->fpu && + addr < (long) &dummy->u_fpvalid) { + set_stopped_child_used_math(child); + ((long *)&child->thread.fpu) + [(addr - (long)&dummy->fpu) >> 2] = data; + ret = 0; + } else if (addr == (long) &dummy->u_fpvalid) { + conditional_stopped_child_used_math(data, child); + ret = 0; + } + 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. */ + long pc; + struct pt_regs *dummy = NULL; + + 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; + } + + pc = get_stack_long(child, (long)&dummy->pc); + + /* Next scheduling will set up UBC */ + if (child->thread.ubc_pc == 0) + ubc_usercnt += 1; + child->thread.ubc_pc = pc; + + 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; + +#ifdef CONFIG_SH_DSP + case PTRACE_GETDSPREGS: { + unsigned long dp; + + ret = -EIO; + dp = ((unsigned long) child) + THREAD_SIZE - + sizeof(struct pt_dspregs); + if (*((int *) (dp - 4)) == SR_FD) { + copy_to_user(addr, (void *) dp, + sizeof(struct pt_dspregs)); + ret = 0; + } + break; + } + + case PTRACE_SETDSPREGS: { + unsigned long dp; + int i; + + ret = -EIO; + dp = ((unsigned long) child) + THREAD_SIZE - + sizeof(struct pt_dspregs); + if (*((int *) (dp - 4)) == SR_FD) { + copy_from_user((void *) dp, addr, + sizeof(struct pt_dspregs)); + ret = 0; + } + break; + } +#endif + default: + ret = ptrace_request(child, request, addr, data); + break; + } +out_tsk: + put_task_struct(child); +out: + unlock_kernel(); + return ret; +} + +asmlinkage void do_syscall_trace(void) +{ + struct task_struct *tsk = current; + + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + if (!(tsk->ptrace & PT_PTRACED)) + return; + /* the 0x80 provides a way for the tracing parent to distinguish + between a syscall stop and SIGTRAP delivery */ + 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; + } +} diff --git a/arch/sh/kernel/semaphore.c b/arch/sh/kernel/semaphore.c new file mode 100644 index 000000000000..a3c24dcbf01d --- /dev/null +++ b/arch/sh/kernel/semaphore.c @@ -0,0 +1,139 @@ +/* + * 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/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/sh/kernel/setup.c b/arch/sh/kernel/setup.c new file mode 100644 index 000000000000..25b9d9ebe858 --- /dev/null +++ b/arch/sh/kernel/setup.c @@ -0,0 +1,649 @@ +/* $Id: setup.c,v 1.30 2003/10/13 07:21:19 lethal Exp $ + * + * linux/arch/sh/kernel/setup.c + * + * Copyright (C) 1999 Niibe Yutaka + * Copyright (C) 2002, 2003 Paul Mundt + */ + +/* + * This file handles the architecture-dependent parts of initialization + */ + +#include <linux/tty.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/initrd.h> +#include <linux/bootmem.h> +#include <linux/console.h> +#include <linux/seq_file.h> +#include <linux/root_dev.h> +#include <linux/utsname.h> +#include <linux/cpu.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/io_generic.h> +#include <asm/sections.h> +#include <asm/irq.h> +#include <asm/setup.h> + +#ifdef CONFIG_SH_KGDB +#include <asm/kgdb.h> +static int kgdb_parse_options(char *options); +#endif +extern void * __rd_start, * __rd_end; +/* + * Machine setup.. + */ + +/* + * Initialize loops_per_jiffy as 10000000 (1000MIPS). + * This value will be used at the very early stage of serial setup. + * The bigger value means no problem. + */ +struct sh_cpuinfo boot_cpu_data = { CPU_SH_NONE, 0, 10000000, }; +struct screen_info screen_info; + +#if defined(CONFIG_SH_UNKNOWN) +struct sh_machine_vector sh_mv; +#endif + +/* We need this to satisfy some external references. */ +struct screen_info screen_info = { + 0, 25, /* orig-x, orig-y */ + 0, /* unused */ + 0, /* orig-video-page */ + 0, /* orig-video-mode */ + 80, /* orig-video-cols */ + 0,0,0, /* ega_ax, ega_bx, ega_cx */ + 25, /* orig-video-lines */ + 0, /* orig-video-isVGA */ + 16 /* orig-video-points */ +}; + +extern void platform_setup(void); +extern char *get_system_type(void); +extern int root_mountflags; + +#define MV_NAME_SIZE 32 + +static struct sh_machine_vector* __init get_mv_byname(const char* name); + +/* + * This is set up by the setup-routine at boot-time + */ +#define PARAM ((unsigned char *)empty_zero_page) + +#define MOUNT_ROOT_RDONLY (*(unsigned long *) (PARAM+0x000)) +#define RAMDISK_FLAGS (*(unsigned long *) (PARAM+0x004)) +#define ORIG_ROOT_DEV (*(unsigned long *) (PARAM+0x008)) +#define LOADER_TYPE (*(unsigned long *) (PARAM+0x00c)) +#define INITRD_START (*(unsigned long *) (PARAM+0x010)) +#define INITRD_SIZE (*(unsigned long *) (PARAM+0x014)) +/* ... */ +#define COMMAND_LINE ((char *) (PARAM+0x100)) + +#define RAMDISK_IMAGE_START_MASK 0x07FF +#define RAMDISK_PROMPT_FLAG 0x8000 +#define RAMDISK_LOAD_FLAG 0x4000 + +static char command_line[COMMAND_LINE_SIZE] = { 0, }; + +struct resource standard_io_resources[] = { + { "dma1", 0x00, 0x1f }, + { "pic1", 0x20, 0x3f }, + { "timer", 0x40, 0x5f }, + { "keyboard", 0x60, 0x6f }, + { "dma page reg", 0x80, 0x8f }, + { "pic2", 0xa0, 0xbf }, + { "dma2", 0xc0, 0xdf }, + { "fpu", 0xf0, 0xff } +}; + +#define STANDARD_IO_RESOURCES (sizeof(standard_io_resources)/sizeof(struct resource)) + +/* System RAM - interrupted by the 640kB-1M hole */ +#define code_resource (ram_resources[3]) +#define data_resource (ram_resources[4]) +static struct resource ram_resources[] = { + { "System RAM", 0x000000, 0x09ffff, IORESOURCE_BUSY }, + { "System RAM", 0x100000, 0x100000, IORESOURCE_BUSY }, + { "Video RAM area", 0x0a0000, 0x0bffff }, + { "Kernel code", 0x100000, 0 }, + { "Kernel data", 0, 0 } +}; + +unsigned long memory_start, memory_end; + +static inline void parse_cmdline (char ** cmdline_p, char mv_name[MV_NAME_SIZE], + struct sh_machine_vector** mvp, + unsigned long *mv_io_base, + int *mv_mmio_enable) +{ + 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'; + + memory_start = (unsigned long)PAGE_OFFSET+__MEMORY_START; + memory_end = memory_start + __MEMORY_SIZE; + + 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; + } + } + if (c == ' ' && !memcmp(from, "sh_mv=", 6)) { + char* mv_end; + char* mv_comma; + int mv_len; + if (to != command_line) + to--; + from += 6; + mv_end = strchr(from, ' '); + if (mv_end == NULL) + mv_end = from + strlen(from); + + mv_comma = strchr(from, ','); + if ((mv_comma != NULL) && (mv_comma < mv_end)) { + int ints[3]; + get_options(mv_comma+1, ARRAY_SIZE(ints), ints); + *mv_io_base = ints[1]; + *mv_mmio_enable = ints[2]; + mv_len = mv_comma - from; + } else { + mv_len = mv_end - from; + } + if (mv_len > (MV_NAME_SIZE-1)) + mv_len = MV_NAME_SIZE-1; + memcpy(mv_name, from, mv_len); + mv_name[mv_len] = '\0'; + from = mv_end; + + *mvp = get_mv_byname(mv_name); + } + c = *(from++); + if (!c) + break; + if (COMMAND_LINE_SIZE <= ++len) + break; + *(to++) = c; + } + *to = '\0'; + *cmdline_p = command_line; +} + +static int __init sh_mv_setup(char **cmdline_p) +{ +#if defined(CONFIG_SH_UNKNOWN) + extern struct sh_machine_vector mv_unknown; +#endif + struct sh_machine_vector *mv = NULL; + char mv_name[MV_NAME_SIZE] = ""; + unsigned long mv_io_base = 0; + int mv_mmio_enable = 0; + + parse_cmdline(cmdline_p, mv_name, &mv, &mv_io_base, &mv_mmio_enable); + +#ifdef CONFIG_SH_GENERIC + if (mv == NULL) { + mv = &mv_unknown; + if (*mv_name != '\0') { + printk("Warning: Unsupported machine %s, using unknown\n", + mv_name); + } + } + sh_mv = *mv; +#endif +#ifdef CONFIG_SH_UNKNOWN + sh_mv = mv_unknown; +#endif + + /* + * Manually walk the vec, fill in anything that the board hasn't yet + * by hand, wrapping to the generic implementation. + */ +#define mv_set(elem) do { \ + if (!sh_mv.mv_##elem) \ + sh_mv.mv_##elem = generic_##elem; \ +} while (0) + + mv_set(inb); mv_set(inw); mv_set(inl); + mv_set(outb); mv_set(outw); mv_set(outl); + + mv_set(inb_p); mv_set(inw_p); mv_set(inl_p); + mv_set(outb_p); mv_set(outw_p); mv_set(outl_p); + + mv_set(insb); mv_set(insw); mv_set(insl); + mv_set(outsb); mv_set(outsw); mv_set(outsl); + + mv_set(readb); mv_set(readw); mv_set(readl); + mv_set(writeb); mv_set(writew); mv_set(writel); + + mv_set(ioremap); + mv_set(iounmap); + + mv_set(isa_port2addr); + mv_set(irq_demux); + +#ifdef CONFIG_SH_UNKNOWN + __set_io_port_base(mv_io_base); +#endif + + return 0; +} + +void __init setup_arch(char **cmdline_p) +{ + unsigned long bootmap_size; + unsigned long start_pfn, max_pfn, max_low_pfn; + +#ifdef CONFIG_EARLY_PRINTK + extern void enable_early_printk(void); + + enable_early_printk(); +#endif +#ifdef CONFIG_CMDLINE_BOOL + strcpy(COMMAND_LINE, CONFIG_CMDLINE); +#endif + + 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 = virt_to_bus(_text); + code_resource.end = virt_to_bus(_etext)-1; + data_resource.start = virt_to_bus(_etext); + data_resource.end = virt_to_bus(_edata)-1; + + sh_mv_setup(cmdline_p); + +#define PFN_UP(x) (((x) + PAGE_SIZE-1) >> PAGE_SHIFT) +#define PFN_DOWN(x) ((x) >> PAGE_SHIFT) +#define PFN_PHYS(x) ((x) << PAGE_SHIFT) + +#ifdef CONFIG_DISCONTIGMEM + NODE_DATA(0)->bdata = &discontig_node_bdata[0]; + NODE_DATA(1)->bdata = &discontig_node_bdata[1]; + + bootmap_size = init_bootmem_node(NODE_DATA(1), + PFN_UP(__MEMORY_START_2ND), + PFN_UP(__MEMORY_START_2ND), + PFN_DOWN(__MEMORY_START_2ND+__MEMORY_SIZE_2ND)); + free_bootmem_node(NODE_DATA(1), __MEMORY_START_2ND, __MEMORY_SIZE_2ND); + reserve_bootmem_node(NODE_DATA(1), __MEMORY_START_2ND, bootmap_size); +#endif + + /* + * Find the highest page frame number we have available + */ + max_pfn = PFN_DOWN(__pa(memory_end)); + + /* + * Determine low and high memory ranges: + */ + max_low_pfn = max_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, + __MEMORY_START>>PAGE_SHIFT, + max_low_pfn); + /* + * Register fully available low RAM pages with the bootmem allocator. + */ + { + unsigned long curr_pfn, last_pfn, pages; + + /* + * We are rounding up the start address of usable memory: + */ + curr_pfn = PFN_UP(__MEMORY_START); + /* + * ... and at the end of the usable range downwards: + */ + last_pfn = PFN_DOWN(__pa(memory_end)); + + if (last_pfn > max_low_pfn) + last_pfn = max_low_pfn; + + pages = last_pfn - curr_pfn; + free_bootmem_node(NODE_DATA(0), PFN_PHYS(curr_pfn), + PFN_PHYS(pages)); + } + + /* + * Reserve the kernel text and + * Reserve the bootmem bitmap. We do this in two steps (first step + * was init_bootmem()), because this catches the (definitely buggy) + * case of us accidentally initializing the bootmem allocator with + * an invalid RAM area. + */ + reserve_bootmem_node(NODE_DATA(0), __MEMORY_START+PAGE_SIZE, + (PFN_PHYS(start_pfn)+bootmap_size+PAGE_SIZE-1)-__MEMORY_START); + + /* + * reserve physical page 0 - it's a special BIOS page on many boxes, + * enabling clean reboots, SMP operation, laptop functions. + */ + reserve_bootmem_node(NODE_DATA(0), __MEMORY_START, PAGE_SIZE); + +#ifdef CONFIG_BLK_DEV_INITRD + ROOT_DEV = MKDEV(RAMDISK_MAJOR, 0); + if (&__rd_start != &__rd_end) { + LOADER_TYPE = 1; + INITRD_START = PHYSADDR((unsigned long)&__rd_start) - __MEMORY_START; + INITRD_SIZE = (unsigned long)&__rd_end - (unsigned long)&__rd_start; + } + + if (LOADER_TYPE && INITRD_START) { + if (INITRD_START + INITRD_SIZE <= (max_low_pfn << PAGE_SHIFT)) { + reserve_bootmem_node(NODE_DATA(0), INITRD_START+__MEMORY_START, INITRD_SIZE); + initrd_start = + 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", + INITRD_START + INITRD_SIZE, + max_low_pfn << PAGE_SHIFT); + initrd_start = 0; + } + } +#endif + +#ifdef CONFIG_DUMMY_CONSOLE + conswitchp = &dummy_con; +#endif + + /* Perform the machine specific initialisation */ + platform_setup(); + + paging_init(); +} + +struct sh_machine_vector* __init get_mv_byname(const char* name) +{ + extern int strcasecmp(const char *, const char *); + extern long __machvec_start, __machvec_end; + struct sh_machine_vector *all_vecs = + (struct sh_machine_vector *)&__machvec_start; + + int i, n = ((unsigned long)&__machvec_end + - (unsigned long)&__machvec_start)/ + sizeof(struct sh_machine_vector); + + for (i = 0; i < n; ++i) { + struct sh_machine_vector *mv = &all_vecs[i]; + if (mv == NULL) + continue; + if (strcasecmp(name, get_system_type()) == 0) { + return mv; + } + } + return NULL; +} + +static struct cpu cpu[NR_CPUS]; + +static int __init topology_init(void) +{ + int cpu_id; + + for (cpu_id = 0; cpu_id < NR_CPUS; cpu_id++) + if (cpu_possible(cpu_id)) + register_cpu(&cpu[cpu_id], cpu_id, NULL); + + return 0; +} + +subsys_initcall(topology_init); + +static const char *cpu_name[] = { + [CPU_SH7604] = "SH7604", + [CPU_SH7705] = "SH7705", + [CPU_SH7708] = "SH7708", + [CPU_SH7729] = "SH7729", + [CPU_SH7300] = "SH7300", + [CPU_SH7750] = "SH7750", + [CPU_SH7750S] = "SH7750S", + [CPU_SH7750R] = "SH7750R", + [CPU_SH7751] = "SH7751", + [CPU_SH7751R] = "SH7751R", + [CPU_SH7760] = "SH7760", + [CPU_SH73180] = "SH73180", + [CPU_ST40RA] = "ST40RA", + [CPU_ST40GX1] = "ST40GX1", + [CPU_SH4_202] = "SH4-202", + [CPU_SH4_501] = "SH4-501", + [CPU_SH_NONE] = "Unknown" +}; + +const char *get_cpu_subtype(void) +{ + return cpu_name[boot_cpu_data.type]; +} + +#ifdef CONFIG_PROC_FS +static const char *cpu_flags[] = { + "none", "fpu", "p2flush", "mmuassoc", "dsp", "perfctr", +}; + +static void show_cpuflags(struct seq_file *m) +{ + unsigned long i; + + seq_printf(m, "cpu flags\t:"); + + if (!cpu_data->flags) { + seq_printf(m, " %s\n", cpu_flags[0]); + return; + } + + for (i = 0; i < cpu_data->flags; i++) + if ((cpu_data->flags & (1 << i))) + seq_printf(m, " %s", cpu_flags[i+1]); + + seq_printf(m, "\n"); +} + +static void show_cacheinfo(struct seq_file *m, const char *type, struct cache_info info) +{ + unsigned int cache_size; + + cache_size = info.ways * info.sets * info.linesz; + + seq_printf(m, "%s size\t: %dKiB\n", type, cache_size >> 10); +} + +/* + * Get CPU information for use by the procfs. + */ +static int show_cpuinfo(struct seq_file *m, void *v) +{ + unsigned int cpu = smp_processor_id(); + + if (!cpu && cpu_online(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: %s\n", system_utsname.machine); + seq_printf(m, "cpu type\t: %s\n", get_cpu_subtype()); + + show_cpuflags(m); + + seq_printf(m, "cache type\t: "); + + /* + * Check for what type of cache we have, we support both the + * unified cache on the SH-2 and SH-3, as well as the harvard + * style cache on the SH-4. + */ + if (test_bit(SH_CACHE_COMBINED, &(boot_cpu_data.icache.flags))) { + seq_printf(m, "unified\n"); + show_cacheinfo(m, "cache", boot_cpu_data.icache); + } else { + seq_printf(m, "split (harvard)\n"); + show_cacheinfo(m, "icache", boot_cpu_data.icache); + show_cacheinfo(m, "dcache", boot_cpu_data.dcache); + } + + seq_printf(m, "bogomips\t: %lu.%02lu\n", + boot_cpu_data.loops_per_jiffy/(500000/HZ), + (boot_cpu_data.loops_per_jiffy/(5000/HZ)) % 100); + +#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); +#ifdef CONFIG_CPU_SUBTYPE_ST40STB1 + PRINT_CLOCK("memory", boot_cpu_data.memory_clock); +#endif + PRINT_CLOCK("module", boot_cpu_data.module_clock); + + return 0; +} + + +static void *c_start(struct seq_file *m, loff_t *pos) +{ + return *pos < NR_CPUS ? cpu_data + *pos : NULL; +} +static void *c_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return c_start(m, pos); +} +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 */ + +#ifdef CONFIG_SH_KGDB +/* + * Parse command-line kgdb options. By default KGDB is enabled, + * entered on error (or other action) using default serial info. + * The command-line option can include a serial port specification + * and an action to override default or configured behavior. + */ +struct kgdb_sermap kgdb_sci_sermap = +{ "ttySC", 5, kgdb_sci_setup, NULL }; + +struct kgdb_sermap *kgdb_serlist = &kgdb_sci_sermap; +struct kgdb_sermap *kgdb_porttype = &kgdb_sci_sermap; + +void kgdb_register_sermap(struct kgdb_sermap *map) +{ + struct kgdb_sermap *last; + + for (last = kgdb_serlist; last->next; last = last->next) + ; + last->next = map; + if (!map->namelen) { + map->namelen = strlen(map->name); + } +} + +static int __init kgdb_parse_options(char *options) +{ + char c; + int baud; + + /* Check for port spec (or use default) */ + + /* Determine port type and instance */ + if (!memcmp(options, "tty", 3)) { + struct kgdb_sermap *map = kgdb_serlist; + + while (map && memcmp(options, map->name, map->namelen)) + map = map->next; + + if (!map) { + KGDB_PRINTK("unknown port spec in %s\n", options); + return -1; + } + + kgdb_porttype = map; + kgdb_serial_setup = map->setup_fn; + kgdb_portnum = options[map->namelen] - '0'; + options += map->namelen + 1; + + options = (*options == ',') ? options+1 : options; + + /* Read optional parameters (baud/parity/bits) */ + baud = simple_strtoul(options, &options, 10); + if (baud != 0) { + kgdb_baud = baud; + + c = toupper(*options); + if (c == 'E' || c == 'O' || c == 'N') { + kgdb_parity = c; + options++; + } + + c = *options; + if (c == '7' || c == '8') { + kgdb_bits = c; + options++; + } + options = (*options == ',') ? options+1 : options; + } + } + + /* Check for action specification */ + if (!memcmp(options, "halt", 4)) { + kgdb_halt = 1; + options += 4; + } else if (!memcmp(options, "disabled", 8)) { + kgdb_enabled = 0; + options += 8; + } + + if (*options) { + KGDB_PRINTK("ignored unknown options: %s\n", options); + return 0; + } + return 1; +} +__setup("kgdb=", kgdb_parse_options); +#endif /* CONFIG_SH_KGDB */ + diff --git a/arch/sh/kernel/sh_bios.c b/arch/sh/kernel/sh_bios.c new file mode 100644 index 000000000000..5b53e10bb9cd --- /dev/null +++ b/arch/sh/kernel/sh_bios.c @@ -0,0 +1,75 @@ +/* + * linux/arch/sh/kernel/sh_bios.c + * C interface for trapping into the standard LinuxSH BIOS. + * + * Copyright (C) 2000 Greg Banks, Mitch Davis + * + */ + +#include <asm/sh_bios.h> + +#define BIOS_CALL_CONSOLE_WRITE 0 +#define BIOS_CALL_READ_BLOCK 1 +#define BIOS_CALL_ETH_NODE_ADDR 10 +#define BIOS_CALL_SHUTDOWN 11 +#define BIOS_CALL_CHAR_OUT 0x1f /* TODO: hack */ +#define BIOS_CALL_GDB_GET_MODE_PTR 0xfe +#define BIOS_CALL_GDB_DETACH 0xff + +static __inline__ long sh_bios_call(long func, long arg0, long arg1, long arg2, long arg3) +{ + register long r0 __asm__("r0") = func; + register long r4 __asm__("r4") = arg0; + register long r5 __asm__("r5") = arg1; + register long r6 __asm__("r6") = arg2; + register long r7 __asm__("r7") = arg3; + __asm__ __volatile__("trapa #0x3f" + : "=z" (r0) + : "0" (r0), "r" (r4), "r" (r5), "r" (r6), "r" (r7) + : "memory"); + return r0; +} + + +void sh_bios_console_write(const char *buf, unsigned int len) +{ + sh_bios_call(BIOS_CALL_CONSOLE_WRITE, (long)buf, (long)len, 0, 0); +} + + +void sh_bios_char_out(char ch) +{ + sh_bios_call(BIOS_CALL_CHAR_OUT, ch, 0, 0, 0); +} + + +int sh_bios_in_gdb_mode(void) +{ + static char queried = 0; + static char *gdb_mode_p = 0; + + if (!queried) + { + /* Query the gdb stub for address of its gdb mode variable */ + long r = sh_bios_call(BIOS_CALL_GDB_GET_MODE_PTR, 0, 0, 0, 0); + if (r != ~0) /* BIOS returns -1 for unknown function */ + gdb_mode_p = (char *)r; + queried = 1; + } + return (gdb_mode_p != 0 ? *gdb_mode_p : 0); +} + +void sh_bios_gdb_detach(void) +{ + sh_bios_call(BIOS_CALL_GDB_DETACH, 0, 0, 0, 0); +} + +void sh_bios_get_node_addr (unsigned char *node_addr) +{ + sh_bios_call(BIOS_CALL_ETH_NODE_ADDR, 0, (long)node_addr, 0, 0); +} + +void sh_bios_shutdown(unsigned int how) +{ + sh_bios_call(BIOS_CALL_SHUTDOWN, how, 0, 0, 0); +} diff --git a/arch/sh/kernel/sh_ksyms.c b/arch/sh/kernel/sh_ksyms.c new file mode 100644 index 000000000000..6954fd62470a --- /dev/null +++ b/arch/sh/kernel/sh_ksyms.c @@ -0,0 +1,126 @@ +#include <linux/config.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/vmalloc.h> +#include <linux/pci.h> +#include <linux/irq.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/tlbflush.h> +#include <asm/cacheflush.h> +#include <asm/checksum.h> + +extern void dump_thread(struct pt_regs *, struct user *); +extern int dump_fpu(struct pt_regs *, elf_fpregset_t *); +extern struct hw_interrupt_type no_irq_type; + +EXPORT_SYMBOL(sh_mv); + +/* platform dependent support */ +EXPORT_SYMBOL(dump_thread); +EXPORT_SYMBOL(dump_fpu); +EXPORT_SYMBOL(iounmap); +EXPORT_SYMBOL(enable_irq); +EXPORT_SYMBOL(disable_irq); +EXPORT_SYMBOL(probe_irq_mask); +EXPORT_SYMBOL(kernel_thread); +EXPORT_SYMBOL(disable_irq_nosync); +EXPORT_SYMBOL(irq_desc); +EXPORT_SYMBOL(no_irq_type); + +EXPORT_SYMBOL(strpbrk); +EXPORT_SYMBOL(strstr); +EXPORT_SYMBOL(strlen); +EXPORT_SYMBOL(strnlen); +EXPORT_SYMBOL(strchr); +EXPORT_SYMBOL(strcat); +EXPORT_SYMBOL(strncat); + +/* PCI exports */ +#ifdef CONFIG_PCI +EXPORT_SYMBOL(pci_alloc_consistent); +EXPORT_SYMBOL(pci_free_consistent); +#endif + +/* mem exports */ +EXPORT_SYMBOL(memchr); +EXPORT_SYMBOL(memcpy); +EXPORT_SYMBOL(memcpy_fromio); +EXPORT_SYMBOL(memcpy_toio); +EXPORT_SYMBOL(memset); +EXPORT_SYMBOL(memset_io); +EXPORT_SYMBOL(memmove); +EXPORT_SYMBOL(memcmp); +EXPORT_SYMBOL(memscan); +EXPORT_SYMBOL(__copy_user); +EXPORT_SYMBOL(boot_cpu_data); + +#ifdef CONFIG_MMU +EXPORT_SYMBOL(get_vm_area); +#endif + +/* semaphore exports */ +EXPORT_SYMBOL(__up); +EXPORT_SYMBOL(__down); +EXPORT_SYMBOL(__down_interruptible); + +EXPORT_SYMBOL(__udelay); +EXPORT_SYMBOL(__ndelay); +EXPORT_SYMBOL(__const_udelay); + +EXPORT_SYMBOL(__div64_32); + +#define DECLARE_EXPORT(name) extern void name(void);EXPORT_SYMBOL(name) + +/* These symbols are generated by the compiler itself */ +DECLARE_EXPORT(__udivsi3); +DECLARE_EXPORT(__udivdi3); +DECLARE_EXPORT(__sdivsi3); +DECLARE_EXPORT(__ashrdi3); +DECLARE_EXPORT(__ashldi3); +DECLARE_EXPORT(__lshrdi3); +DECLARE_EXPORT(__movstr); + +EXPORT_SYMBOL(strcpy); + +#ifdef CONFIG_CPU_SH4 +DECLARE_EXPORT(__movstr_i4_even); +DECLARE_EXPORT(__movstr_i4_odd); +DECLARE_EXPORT(__movstrSI12_i4); + +/* needed by some modules */ +EXPORT_SYMBOL(flush_cache_all); +EXPORT_SYMBOL(flush_cache_range); +EXPORT_SYMBOL(flush_dcache_page); +EXPORT_SYMBOL(__flush_purge_region); +#endif + +#if defined(CONFIG_SH7705_CACHE_32KB) +EXPORT_SYMBOL(flush_cache_all); +EXPORT_SYMBOL(flush_cache_range); +EXPORT_SYMBOL(flush_dcache_page); +EXPORT_SYMBOL(__flush_purge_region); +#endif + +EXPORT_SYMBOL(flush_tlb_page); +EXPORT_SYMBOL(__down_trylock); + +#ifdef CONFIG_SMP +EXPORT_SYMBOL(synchronize_irq); +#endif + +EXPORT_SYMBOL(csum_partial); +EXPORT_SYMBOL(csum_ipv6_magic); +EXPORT_SYMBOL(consistent_sync); +EXPORT_SYMBOL(clear_page); diff --git a/arch/sh/kernel/signal.c b/arch/sh/kernel/signal.c new file mode 100644 index 000000000000..06f1b47eded9 --- /dev/null +++ b/arch/sh/kernel/signal.c @@ -0,0 +1,607 @@ +/* + * linux/arch/sh/kernel/signal.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * 1997-11-28 Modified for POSIX.1b signals by Richard Henderson + * + * SuperH version: Copyright (C) 1999, 2000 Niibe Yutaka & Kaz Kojima + * + */ + +#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/ptrace.h> +#include <linux/unistd.h> +#include <linux/stddef.h> +#include <linux/tty.h> +#include <linux/personality.h> +#include <linux/binfmts.h> + +#include <asm/ucontext.h> +#include <asm/uaccess.h> +#include <asm/pgtable.h> +#include <asm/cacheflush.h> + +#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 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); + + regs.regs[0] = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(®s, &saveset)) + return -EINTR; + } +} + +asmlinkage int +sys_rt_sigsuspend(sigset_t *unewset, size_t sigsetsize, + 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); + + regs.regs[0] = -EINTR; + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + if (do_signal(®s, &saveset)) + 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 r6, unsigned long r7, + struct pt_regs regs) +{ + return do_sigaltstack(uss, uoss, regs.regs[15]); +} + + +/* + * Do a signal return; undo the signal stack. + */ + +#define MOVW(n) (0x9300|((n)-2)) /* Move mem word at PC+n to R3 */ +#define TRAP16 0xc310 /* Syscall w/no args (NR in R3) */ +#define OR_R0_R0 0x200b /* or r0,r0 (insert to avoid hardware bug) */ + +struct sigframe +{ + struct sigcontext sc; + unsigned long extramask[_NSIG_WORDS-1]; + u16 retcode[8]; +}; + +struct rt_sigframe +{ + struct siginfo info; + struct ucontext uc; + u16 retcode[8]; +}; + +#ifdef CONFIG_SH_FPU +static inline int restore_sigcontext_fpu(struct sigcontext __user *sc) +{ + struct task_struct *tsk = current; + + if (!(cpu_data->flags & CPU_HAS_FPU)) + return 0; + + set_used_math(); + return __copy_from_user(&tsk->thread.fpu.hard, &sc->sc_fpregs[0], + sizeof(long)*(16*2+2)); +} + +static inline int save_sigcontext_fpu(struct sigcontext __user *sc, + struct pt_regs *regs) +{ + struct task_struct *tsk = current; + + if (!(cpu_data->flags & CPU_HAS_FPU)) + return 0; + + if (!used_math()) { + __put_user(0, &sc->sc_ownedfp); + return 0; + } + + __put_user(1, &sc->sc_ownedfp); + + /* This will cause a "finit" to be triggered by the next + attempted FPU operation by the 'current' process. + */ + clear_used_math(); + + unlazy_fpu(tsk, regs); + return __copy_to_user(&sc->sc_fpregs[0], &tsk->thread.fpu.hard, + sizeof(long)*(16*2+2)); +} +#endif /* CONFIG_SH_FPU */ + +static int +restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc, int *r0_p) +{ + unsigned int err = 0; + +#define COPY(x) err |= __get_user(regs->x, &sc->sc_##x) + 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(gbr); COPY(mach); + COPY(macl); COPY(pr); + COPY(sr); COPY(pc); +#undef COPY + +#ifdef CONFIG_SH_FPU + if (cpu_data->flags & CPU_HAS_FPU) { + int owned_fp; + struct task_struct *tsk = current; + + regs->sr |= SR_FD; /* Release FPU */ + clear_fpu(tsk, regs); + clear_used_math(); + __get_user (owned_fp, &sc->sc_ownedfp); + if (owned_fp) + err |= restore_sigcontext_fpu(sc); + } +#endif + + regs->tra = -1; /* disable syscall checks */ + err |= __get_user(*r0_p, &sc->sc_regs[0]); + return err; +} + +asmlinkage int sys_sigreturn(unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + struct sigframe __user *frame = (struct sigframe __user *)regs.regs[15]; + sigset_t set; + int r0; + + 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(®s, &frame->sc, &r0)) + goto badframe; + return r0; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +asmlinkage int sys_rt_sigreturn(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 *)regs.regs[15]; + sigset_t set; + stack_t st; + int r0; + + 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(®s, &frame->uc.uc_mcontext, &r0)) + goto badframe; + + 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, regs.regs[15]); + + return r0; + +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; + +#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(gbr); COPY(mach); + COPY(macl); COPY(pr); + COPY(sr); COPY(pc); +#undef COPY + +#ifdef CONFIG_SH_FPU + err |= save_sigcontext_fpu(sc, regs); +#endif + + /* non-iBCS2 extensions.. */ + 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) { + if (sas_ss_flags(sp) == 0) + sp = current->sas_ss_sp + current->sas_ss_size; + } + + return (void __user *)((sp - frame_size) & -8ul); +} + +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[15], 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]); + + if (_NSIG_WORDS > 1) { + err |= __copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask)); + } + + /* Set up to return from userspace. If provided, use a stub + already in userspace. */ + if (ka->sa.sa_flags & SA_RESTORER) { + regs->pr = (unsigned long) ka->sa.sa_restorer; + } else { + /* Generate return code (system call to sigreturn) */ + err |= __put_user(MOVW(7), &frame->retcode[0]); + err |= __put_user(TRAP16, &frame->retcode[1]); + err |= __put_user(OR_R0_R0, &frame->retcode[2]); + err |= __put_user(OR_R0_R0, &frame->retcode[3]); + err |= __put_user(OR_R0_R0, &frame->retcode[4]); + err |= __put_user(OR_R0_R0, &frame->retcode[5]); + err |= __put_user(OR_R0_R0, &frame->retcode[6]); + err |= __put_user((__NR_sigreturn), &frame->retcode[7]); + regs->pr = (unsigned long) frame->retcode; + } + + if (err) + goto give_sigsegv; + + /* Set up registers for signal handler */ + regs->regs[15] = (unsigned long) frame; + regs->regs[4] = signal; /* Arg for signal handler */ + regs->regs[5] = 0; + regs->regs[6] = (unsigned long) &frame->sc; + regs->pc = (unsigned long) ka->sa.sa_handler; + + set_fs(USER_DS); + +#if DEBUG_SIG + printk("SIG deliver (%s:%d): sp=%p pc=%08lx pr=%08lx\n", + current->comm, current->pid, frame, regs->pc, regs->pr); +#endif + + flush_cache_sigtramp(regs->pr); + if ((-regs->pr & (L1_CACHE_BYTES-1)) < sizeof(frame->retcode)) + flush_cache_sigtramp(regs->pr + L1_CACHE_BYTES); + 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[15], 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 |= copy_siginfo_to_user(&frame->info, info); + + /* 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[15]), + &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)); + + /* Set up to return from userspace. If provided, use a stub + already in userspace. */ + if (ka->sa.sa_flags & SA_RESTORER) { + regs->pr = (unsigned long) ka->sa.sa_restorer; + } else { + /* Generate return code (system call to rt_sigreturn) */ + err |= __put_user(MOVW(7), &frame->retcode[0]); + err |= __put_user(TRAP16, &frame->retcode[1]); + err |= __put_user(OR_R0_R0, &frame->retcode[2]); + err |= __put_user(OR_R0_R0, &frame->retcode[3]); + err |= __put_user(OR_R0_R0, &frame->retcode[4]); + err |= __put_user(OR_R0_R0, &frame->retcode[5]); + err |= __put_user(OR_R0_R0, &frame->retcode[6]); + err |= __put_user((__NR_rt_sigreturn), &frame->retcode[7]); + regs->pr = (unsigned long) frame->retcode; + } + + if (err) + goto give_sigsegv; + + /* Set up registers for signal handler */ + regs->regs[15] = (unsigned long) frame; + regs->regs[4] = signal; /* Arg for signal handler */ + regs->regs[5] = (unsigned long) &frame->info; + regs->regs[6] = (unsigned long) &frame->uc; + regs->pc = (unsigned long) ka->sa.sa_handler; + + set_fs(USER_DS); + +#if DEBUG_SIG + printk("SIG deliver (%s:%d): sp=%p pc=%08lx pr=%08lx\n", + current->comm, current->pid, frame, regs->pc, regs->pr); +#endif + + flush_cache_sigtramp(regs->pr); + if ((-regs->pr & (L1_CACHE_BYTES-1)) < sizeof(frame->retcode)) + flush_cache_sigtramp(regs->pr + L1_CACHE_BYTES); + return; + +give_sigsegv: + force_sigsegv(sig, current); +} + +/* + * OK, we're invoking a handler + */ + +static void +handle_signal(unsigned long sig, struct k_sigaction *ka, siginfo_t *info, + sigset_t *oldset, struct pt_regs *regs) +{ + /* Are we from a system call? */ + if (regs->tra >= 0) { + /* If so, check system call restarting.. */ + switch (regs->regs[0]) { + case -ERESTARTNOHAND: + regs->regs[0] = -EINTR; + break; + + case -ERESTARTSYS: + if (!(ka->sa.sa_flags & SA_RESTART)) { + regs->regs[0] = -EINTR; + break; + } + /* fallthrough */ + case -ERESTARTNOINTR: + regs->pc -= 2; + } + } else { + /* gUSA handling */ +#ifdef CONFIG_PREEMPT + unsigned long flags; + + local_irq_save(flags); +#endif + if (regs->regs[15] >= 0xc0000000) { + int offset = (int)regs->regs[15]; + + /* Reset stack pointer: clear critical region mark */ + regs->regs[15] = regs->regs[1]; + if (regs->pc < regs->regs[0]) + /* Go to rewind point #1 */ + regs->pc = regs->regs[0] + offset - 2; + } +#ifdef CONFIG_PREEMPT + local_irq_restore(flags); +#endif + } + + /* 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_ONESHOT) + ka->sa.sa_handler = SIG_DFL; + + 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, NULL); + if (signr > 0) { + /* Whee! Actually deliver the signal. */ + handle_signal(signr, &ka, &info, oldset, regs); + return 1; + } + + no_signal: + /* Did we come from a system call? */ + if (regs->tra >= 0) { + /* Restart the system call - no handlers present */ + if (regs->regs[0] == -ERESTARTNOHAND || + regs->regs[0] == -ERESTARTSYS || + regs->regs[0] == -ERESTARTNOINTR || + regs->regs[0] == -ERESTART_RESTARTBLOCK) { + regs->pc -= 2; + } + } + return 0; +} diff --git a/arch/sh/kernel/smp.c b/arch/sh/kernel/smp.c new file mode 100644 index 000000000000..56a39d69e080 --- /dev/null +++ b/arch/sh/kernel/smp.c @@ -0,0 +1,199 @@ +/* + * arch/sh/kernel/smp.c + * + * SMP support for the SuperH processors. + * + * Copyright (C) 2002, 2003 Paul Mundt + * + * 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. + */ +#include <linux/config.h> +#include <linux/cache.h> +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/threads.h> +#include <linux/module.h> +#include <linux/time.h> +#include <linux/timex.h> +#include <linux/sched.h> + +#include <asm/atomic.h> +#include <asm/processor.h> +#include <asm/system.h> +#include <asm/mmu_context.h> +#include <asm/smp.h> + +/* + * This was written with the Sega Saturn (SMP SH-2 7604) in mind, + * but is designed to be usable regardless if there's an MMU + * present or not. + */ +struct sh_cpuinfo cpu_data[NR_CPUS]; + +extern void per_cpu_trap_init(void); + +cpumask_t cpu_possible_map; +cpumask_t cpu_online_map; +static atomic_t cpus_booted = ATOMIC_INIT(0); + +/* These are defined by the board-specific code. */ + +/* + * Cause the function described by call_data to be executed on the passed + * cpu. When the function has finished, increment the finished field of + * call_data. + */ +void __smp_send_ipi(unsigned int cpu, unsigned int action); + +/* + * Find the number of available processors + */ +unsigned int __smp_probe_cpus(void); + +/* + * Start a particular processor + */ +void __smp_slave_init(unsigned int cpu); + +/* + * Run specified function on a particular processor. + */ +void __smp_call_function(unsigned int cpu); + +static inline void __init smp_store_cpu_info(unsigned int cpu) +{ + cpu_data[cpu].loops_per_jiffy = loops_per_jiffy; +} + +void __init smp_prepare_cpus(unsigned int max_cpus) +{ + unsigned int cpu = smp_processor_id(); + int i; + + atomic_set(&cpus_booted, 1); + smp_store_cpu_info(cpu); + + for (i = 0; i < __smp_probe_cpus(); i++) + cpu_set(i, cpu_possible_map); +} + +void __devinit smp_prepare_boot_cpu(void) +{ + unsigned int cpu = smp_processor_id(); + + cpu_set(cpu, cpu_online_map); + cpu_set(cpu, cpu_possible_map); +} + +int __cpu_up(unsigned int cpu) +{ + struct task_struct *tsk; + + tsk = fork_idle(cpu); + + if (IS_ERR(tsk)) + panic("Failed forking idle task for cpu %d\n", cpu); + + tsk->thread_info->cpu = cpu; + + cpu_set(cpu, cpu_online_map); + + return 0; +} + +int start_secondary(void *unused) +{ + unsigned int cpu = smp_processor_id(); + + atomic_inc(&init_mm.mm_count); + current->active_mm = &init_mm; + + smp_store_cpu_info(cpu); + + __smp_slave_init(cpu); + per_cpu_trap_init(); + + atomic_inc(&cpus_booted); + + cpu_idle(); + return 0; +} + +void __init smp_cpus_done(unsigned int max_cpus) +{ + smp_mb(); +} + +void smp_send_reschedule(int cpu) +{ + __smp_send_ipi(cpu, SMP_MSG_RESCHEDULE); +} + +static void stop_this_cpu(void *unused) +{ + cpu_clear(smp_processor_id(), cpu_online_map); + local_irq_disable(); + + for (;;) + cpu_relax(); +} + +void smp_send_stop(void) +{ + smp_call_function(stop_this_cpu, 0, 1, 0); +} + + +struct smp_fn_call_struct smp_fn_call = { + .lock = SPIN_LOCK_UNLOCKED, + .finished = ATOMIC_INIT(0), +}; + +/* + * The caller of this wants the passed function to run on every cpu. If wait + * is set, wait until all cpus have finished the function before returning. + * The lock is here to protect the call structure. + * You must not call this function with disabled interrupts or from a + * hardware interrupt handler or from a bottom half handler. + */ +int smp_call_function(void (*func)(void *info), void *info, int retry, int wait) +{ + unsigned int nr_cpus = atomic_read(&cpus_booted); + int i; + + if (nr_cpus < 2) + return 0; + + /* Can deadlock when called with interrupts disabled */ + WARN_ON(irqs_disabled()); + + spin_lock(&smp_fn_call.lock); + + atomic_set(&smp_fn_call.finished, 0); + smp_fn_call.fn = func; + smp_fn_call.data = info; + + for (i = 0; i < nr_cpus; i++) + if (i != smp_processor_id()) + __smp_call_function(i); + + if (wait) + while (atomic_read(&smp_fn_call.finished) != (nr_cpus - 1)); + + spin_unlock(&smp_fn_call.lock); + + return 0; +} + +/* Not really SMP stuff ... */ +int setup_profiling_timer(unsigned int multiplier) +{ + return 0; +} + diff --git a/arch/sh/kernel/sys_sh.c b/arch/sh/kernel/sys_sh.c new file mode 100644 index 000000000000..df5ac294c379 --- /dev/null +++ b/arch/sh/kernel/sys_sh.c @@ -0,0 +1,289 @@ +/* + * linux/arch/sh/kernel/sys_sh.c + * + * This file contains various random system calls that + * have a non-standard calling sequence on the Linux/SuperH + * platform. + * + * Taken from i386 version. + */ + +#include <linux/errno.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/syscalls.h> +#include <linux/mman.h> +#include <linux/file.h> +#include <linux/utsname.h> + +#include <asm/uaccess.h> +#include <asm/ipc.h> + +/* + * sys_pipe() is the normal C calling standard for creating + * a pipe. It's not the way Unix traditionally does this, though. + */ +asmlinkage int sys_pipe(unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + int fd[2]; + int error; + + error = do_pipe(fd); + if (!error) { + regs.regs[1] = fd[1]; + return fd[0]; + } + return error; +} + +#if defined(HAVE_ARCH_UNMAPPED_AREA) +/* + * 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 mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long start_addr; + + 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) { + if (flags & MAP_PRIVATE) + addr = PAGE_ALIGN(addr); + else + addr = COLOUR_ALIGN(addr); + vma = find_vma(mm, addr); + if (TASK_SIZE - len >= addr && + (!vma || addr + len <= vma->vm_start)) + return addr; + } + if (flags & MAP_PRIVATE) + addr = PAGE_ALIGN(mm->free_area_cache); + else + addr = COLOUR_ALIGN(mm->free_area_cache); + start_addr = addr; + +full_search: + for (vma = find_vma(mm, addr); ; vma = vma->vm_next) { + /* At this point: (!vma || addr < vma->vm_end). */ + if (TASK_SIZE - len < addr) { + /* + * Start a new search - just in case we missed + * some holes. + */ + if (start_addr != TASK_UNMAPPED_BASE) { + start_addr = addr = TASK_UNMAPPED_BASE; + goto full_search; + } + return -ENOMEM; + } + if (!vma || addr + len <= vma->vm_start) { + /* + * Remember the place where we stopped the search: + */ + mm->free_area_cache = addr + len; + return addr; + } + addr = vma->vm_end; + if (!(flags & MAP_PRIVATE)) + addr = COLOUR_ALIGN(addr); + } +} +#endif + +static inline long +do_mmap2(unsigned long addr, unsigned long len, unsigned long prot, + unsigned long flags, int 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 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); +} + +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); +} + +/* + * 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; +} + +asmlinkage ssize_t sys_pread_wrapper(unsigned int fd, char * buf, + size_t count, long dummy, loff_t pos) +{ + return sys_pread64(fd, buf, count, pos); +} + +asmlinkage ssize_t sys_pwrite_wrapper(unsigned int fd, const char * buf, + size_t count, long dummy, loff_t pos) +{ + return sys_pwrite64(fd, buf, count, pos); +} + +asmlinkage int sys_fadvise64_64_wrapper(int fd, u32 offset0, u32 offset1, + u32 len0, u32 len1, int advice) +{ +#ifdef __LITTLE_ENDIAN__ + return sys_fadvise64_64(fd, (u64)offset1 << 32 | offset0, + (u64)len1 << 32 | len0, advice); +#else + return sys_fadvise64_64(fd, (u64)offset0 << 32 | offset1, + (u64)len0 << 32 | len1, advice); +#endif +} diff --git a/arch/sh/kernel/time.c b/arch/sh/kernel/time.c new file mode 100644 index 000000000000..df7a9b9d4cbf --- /dev/null +++ b/arch/sh/kernel/time.c @@ -0,0 +1,657 @@ +/* + * arch/sh/kernel/time.c + * + * Copyright (C) 1999 Tetsuya Okada & Niibe Yutaka + * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org> + * Copyright (C) 2002, 2003, 2004 Paul Mundt + * Copyright (C) 2002 M. R. Brown <mrbrown@linux-sh.org> + * + * Some code taken from i386 version. + * Copyright (C) 1991, 1992, 1995 Linus Torvalds + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/module.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/smp.h> +#include <linux/profile.h> + +#include <asm/processor.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/delay.h> +#include <asm/machvec.h> +#include <asm/rtc.h> +#include <asm/freq.h> +#include <asm/cpu/timer.h> +#ifdef CONFIG_SH_KGDB +#include <asm/kgdb.h> +#endif + +#include <linux/timex.h> +#include <linux/irq.h> + +#define TMU_TOCR_INIT 0x00 +#define TMU0_TCR_INIT 0x0020 +#define TMU_TSTR_INIT 1 + +#define TMU0_TCR_CALIB 0x0000 + +#ifdef CONFIG_CPU_SUBTYPE_ST40STB1 +#define CLOCKGEN_MEMCLKCR 0xbb040038 +#define MEMCLKCR_RATIO_MASK 0x7 +#endif /* CONFIG_CPU_SUBTYPE_ST40STB1 */ + +extern unsigned long wall_jiffies; +#define TICK_SIZE (tick_nsec / 1000) +DEFINE_SPINLOCK(tmu0_lock); + +u64 jiffies_64 = INITIAL_JIFFIES; + +EXPORT_SYMBOL(jiffies_64); + +/* XXX: Can we initialize this in a routine somewhere? Dreamcast doesn't want + * these routines anywhere... */ +#ifdef CONFIG_SH_RTC +void (*rtc_get_time)(struct timespec *) = sh_rtc_gettimeofday; +int (*rtc_set_time)(const time_t) = sh_rtc_settimeofday; +#else +void (*rtc_get_time)(struct timespec *); +int (*rtc_set_time)(const time_t); +#endif + +#if defined(CONFIG_CPU_SUBTYPE_SH7300) +static int md_table[] = { 1, 2, 3, 4, 6, 8, 12 }; +#endif +#if defined(CONFIG_CPU_SH3) +static int stc_multipliers[] = { 1, 2, 3, 4, 6, 1, 1, 1 }; +static int stc_values[] = { 0, 1, 4, 2, 5, 0, 0, 0 }; +#define bfc_divisors stc_multipliers +#define bfc_values stc_values +static int ifc_divisors[] = { 1, 2, 3, 4, 1, 1, 1, 1 }; +static int ifc_values[] = { 0, 1, 4, 2, 0, 0, 0, 0 }; +static int pfc_divisors[] = { 1, 2, 3, 4, 6, 1, 1, 1 }; +static int pfc_values[] = { 0, 1, 4, 2, 5, 0, 0, 0 }; +#elif defined(CONFIG_CPU_SH4) +#if defined(CONFIG_CPU_SUBTYPE_SH73180) +static int ifc_divisors[] = { 1, 2, 3, 4, 6, 8, 12, 16 }; +static int ifc_values[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; +#define bfc_divisors ifc_divisors /* Same */ +#define bfc_values ifc_values +#define pfc_divisors ifc_divisors /* Same */ +#define pfc_values ifc_values +#else +static int ifc_divisors[] = { 1, 2, 3, 4, 6, 8, 1, 1 }; +static int ifc_values[] = { 0, 1, 2, 3, 0, 4, 0, 5 }; +#define bfc_divisors ifc_divisors /* Same */ +#define bfc_values ifc_values +static int pfc_divisors[] = { 2, 3, 4, 6, 8, 2, 2, 2 }; +static int pfc_values[] = { 0, 0, 1, 2, 0, 3, 0, 4 }; +#endif +#else +#error "Unknown ifc/bfc/pfc/stc values for this processor" +#endif + +/* + * Scheduler clock - returns current time in nanosec units. + */ +unsigned long long sched_clock(void) +{ + return (unsigned long long)jiffies * (1000000000 / HZ); +} + +static unsigned long do_gettimeoffset(void) +{ + int count; + unsigned long flags; + + static int count_p = 0x7fffffff; /* for the first call after boot */ + static unsigned long jiffies_p = 0; + + /* + * cache volatile jiffies temporarily; we have IRQs turned off. + */ + unsigned long jiffies_t; + + spin_lock_irqsave(&tmu0_lock, flags); + /* timer count may underflow right here */ + count = ctrl_inl(TMU0_TCNT); /* read the latched count */ + + jiffies_t = jiffies; + + /* + * avoiding timer inconsistencies (they are rare, but they happen)... + * there is one kind of problem that must be avoided here: + * 1. the timer counter underflows + */ + + if( jiffies_t == jiffies_p ) { + if( count > count_p ) { + /* the nutcase */ + + if(ctrl_inw(TMU0_TCR) & 0x100) { /* Check UNF bit */ + /* + * We cannot detect lost timer interrupts ... + * well, that's why we call them lost, don't we? :) + * [hmm, on the Pentium and Alpha we can ... sort of] + */ + count -= LATCH; + } else { + printk("do_slow_gettimeoffset(): hardware timer problem?\n"); + } + } + } else + jiffies_p = jiffies_t; + + count_p = count; + spin_unlock_irqrestore(&tmu0_lock, flags); + + count = ((LATCH-1) - count) * TICK_SIZE; + count = (count + LATCH/2) / LATCH; + + return count; +} + +void do_gettimeofday(struct timeval *tv) +{ + unsigned long seq; + unsigned long usec, sec; + unsigned long lost; + + do { + seq = read_seqbegin(&xtime_lock); + usec = do_gettimeoffset(); + + lost = jiffies - wall_jiffies; + if (lost) + usec += lost * (1000000 / HZ); + + sec = xtime.tv_sec; + usec += xtime.tv_nsec / 1000; + } while (read_seqretry(&xtime_lock, seq)); + + while (usec >= 1000000) { + usec -= 1000000; + sec++; + } + + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +EXPORT_SYMBOL(do_gettimeofday); + +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 * (do_gettimeoffset() + + (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; +} + +EXPORT_SYMBOL(do_settimeofday); + +/* last time the RTC clock got updated */ +static long last_rtc_update; + +/* + * 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) +{ + do_timer(regs); +#ifndef CONFIG_SMP + update_process_times(user_mode(regs)); +#endif + profile_tick(CPU_PROFILING, regs); + +#ifdef CONFIG_HEARTBEAT + if (sh_mv.mv_heartbeat != NULL) + sh_mv.mv_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 (rtc_set_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_seqlock(&xtime_lock); + do_timer_interrupt(irq, NULL, regs); + write_sequnlock(&xtime_lock); + + return IRQ_HANDLED; +} + +/* + * Hah! We'll see if this works (switching from usecs to nsecs). + */ +static unsigned int __init get_timer_frequency(void) +{ + u32 freq; + struct timespec ts1, ts2; + unsigned long diff_nsec; + unsigned long factor; + + /* Setup the timer: We don't want to generate interrupts, just + * have it count down at its natural rate. + */ + ctrl_outb(0, TMU_TSTR); +#if !defined(CONFIG_CPU_SUBTYPE_SH7300) + ctrl_outb(TMU_TOCR_INIT, TMU_TOCR); +#endif + ctrl_outw(TMU0_TCR_CALIB, TMU0_TCR); + ctrl_outl(0xffffffff, TMU0_TCOR); + ctrl_outl(0xffffffff, TMU0_TCNT); + + rtc_get_time(&ts2); + + do { + rtc_get_time(&ts1); + } while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec); + + /* actually start the timer */ + ctrl_outb(TMU_TSTR_INIT, TMU_TSTR); + + do { + rtc_get_time(&ts2); + } while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec); + + freq = 0xffffffff - ctrl_inl(TMU0_TCNT); + if (ts2.tv_nsec < ts1.tv_nsec) { + ts2.tv_nsec += 1000000000; + ts2.tv_sec--; + } + + diff_nsec = (ts2.tv_sec - ts1.tv_sec) * 1000000000 + (ts2.tv_nsec - ts1.tv_nsec); + + /* this should work well if the RTC has a precision of n Hz, where + * n is an integer. I don't think we have to worry about the other + * cases. */ + factor = (1000000000 + diff_nsec/2) / diff_nsec; + + if (factor * diff_nsec > 1100000000 || + factor * diff_nsec < 900000000) + panic("weird RTC (diff_nsec %ld)", diff_nsec); + + return freq * factor; +} + +void (*board_time_init)(void); +void (*board_timer_setup)(struct irqaction *irq); + +static unsigned int sh_pclk_freq __initdata = CONFIG_SH_PCLK_FREQ; + +static int __init sh_pclk_setup(char *str) +{ + unsigned int freq; + + if (get_option(&str, &freq)) + sh_pclk_freq = freq; + + return 1; +} +__setup("sh_pclk=", sh_pclk_setup); + +static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL}; + +void get_current_frequency_divisors(unsigned int *ifc, unsigned int *bfc, unsigned int *pfc) +{ + unsigned int frqcr = ctrl_inw(FRQCR); + +#if defined(CONFIG_CPU_SH3) +#if defined(CONFIG_CPU_SUBTYPE_SH7300) + *ifc = md_table[((frqcr & 0x0070) >> 4)]; + *bfc = md_table[((frqcr & 0x0700) >> 8)]; + *pfc = md_table[frqcr & 0x0007]; +#elif defined(CONFIG_CPU_SUBTYPE_SH7705) + *bfc = stc_multipliers[(frqcr & 0x0300) >> 8]; + *ifc = ifc_divisors[(frqcr & 0x0030) >> 4]; + *pfc = pfc_divisors[frqcr & 0x0003]; +#else + unsigned int tmp; + + tmp = (frqcr & 0x8000) >> 13; + tmp |= (frqcr & 0x0030) >> 4; + *bfc = stc_multipliers[tmp]; + tmp = (frqcr & 0x4000) >> 12; + tmp |= (frqcr & 0x000c) >> 2; + *ifc = ifc_divisors[tmp]; + tmp = (frqcr & 0x2000) >> 11; + tmp |= frqcr & 0x0003; + *pfc = pfc_divisors[tmp]; +#endif +#elif defined(CONFIG_CPU_SH4) +#if defined(CONFIG_CPU_SUBTYPE_SH73180) + *ifc = ifc_divisors[(frqcr>> 20) & 0x0007]; + *bfc = bfc_divisors[(frqcr>> 12) & 0x0007]; + *pfc = pfc_divisors[frqcr & 0x0007]; +#else + *ifc = ifc_divisors[(frqcr >> 6) & 0x0007]; + *bfc = bfc_divisors[(frqcr >> 3) & 0x0007]; + *pfc = pfc_divisors[frqcr & 0x0007]; +#endif +#endif +} + +/* + * This bit of ugliness builds up accessor routines to get at both + * the divisors and the physical values. + */ +#define _FREQ_TABLE(x) \ + unsigned int get_##x##_divisor(unsigned int value) \ + { return x##_divisors[value]; } \ + \ + unsigned int get_##x##_value(unsigned int divisor) \ + { return x##_values[(divisor - 1)]; } + +_FREQ_TABLE(ifc); +_FREQ_TABLE(bfc); +_FREQ_TABLE(pfc); + +#ifdef CONFIG_CPU_SUBTYPE_ST40STB1 + +/* + * The ST40 divisors are totally different so we set the cpu data + * clocks using a different algorithm + * + * I've just plugged this from the 2.4 code + * - Alex Bennee <kernel-hacker@bennee.com> + */ +#define CCN_PVR_CHIP_SHIFT 24 +#define CCN_PVR_CHIP_MASK 0xff +#define CCN_PVR_CHIP_ST40STB1 0x4 + + +struct frqcr_data { + unsigned short frqcr; + + struct { + unsigned char multiplier; + unsigned char divisor; + } factor[3]; +}; + +static struct frqcr_data st40_frqcr_table[] = { + { 0x000, {{1,1}, {1,1}, {1,2}}}, + { 0x002, {{1,1}, {1,1}, {1,4}}}, + { 0x004, {{1,1}, {1,1}, {1,8}}}, + { 0x008, {{1,1}, {1,2}, {1,2}}}, + { 0x00A, {{1,1}, {1,2}, {1,4}}}, + { 0x00C, {{1,1}, {1,2}, {1,8}}}, + { 0x011, {{1,1}, {2,3}, {1,6}}}, + { 0x013, {{1,1}, {2,3}, {1,3}}}, + { 0x01A, {{1,1}, {1,2}, {1,4}}}, + { 0x01C, {{1,1}, {1,2}, {1,8}}}, + { 0x023, {{1,1}, {2,3}, {1,3}}}, + { 0x02C, {{1,1}, {1,2}, {1,8}}}, + { 0x048, {{1,2}, {1,2}, {1,4}}}, + { 0x04A, {{1,2}, {1,2}, {1,6}}}, + { 0x04C, {{1,2}, {1,2}, {1,8}}}, + { 0x05A, {{1,2}, {1,3}, {1,6}}}, + { 0x05C, {{1,2}, {1,3}, {1,6}}}, + { 0x063, {{1,2}, {1,4}, {1,4}}}, + { 0x06C, {{1,2}, {1,4}, {1,8}}}, + { 0x091, {{1,3}, {1,3}, {1,6}}}, + { 0x093, {{1,3}, {1,3}, {1,6}}}, + { 0x0A3, {{1,3}, {1,6}, {1,6}}}, + { 0x0DA, {{1,4}, {1,4}, {1,8}}}, + { 0x0DC, {{1,4}, {1,4}, {1,8}}}, + { 0x0EC, {{1,4}, {1,8}, {1,8}}}, + { 0x123, {{1,4}, {1,4}, {1,8}}}, + { 0x16C, {{1,4}, {1,8}, {1,8}}}, +}; + +struct memclk_data { + unsigned char multiplier; + unsigned char divisor; +}; + +static struct memclk_data st40_memclk_table[8] = { + {1,1}, // 000 + {1,2}, // 001 + {1,3}, // 010 + {2,3}, // 011 + {1,4}, // 100 + {1,6}, // 101 + {1,8}, // 110 + {1,8} // 111 +}; + +static void st40_specific_time_init(unsigned int module_clock, unsigned short frqcr) +{ + unsigned int cpu_clock, master_clock, bus_clock, memory_clock; + struct frqcr_data *d; + int a; + unsigned long memclkcr; + struct memclk_data *e; + + for (a = 0; a < ARRAY_SIZE(st40_frqcr_table); a++) { + d = &st40_frqcr_table[a]; + + if (d->frqcr == (frqcr & 0x1ff)) + break; + } + + if (a == ARRAY_SIZE(st40_frqcr_table)) { + d = st40_frqcr_table; + + printk("ERROR: Unrecognised FRQCR value (0x%x), " + "using default multipliers\n", frqcr); + } + + memclkcr = ctrl_inl(CLOCKGEN_MEMCLKCR); + e = &st40_memclk_table[memclkcr & MEMCLKCR_RATIO_MASK]; + + printk(KERN_INFO "Clock multipliers: CPU: %d/%d Bus: %d/%d " + "Mem: %d/%d Periph: %d/%d\n", + d->factor[0].multiplier, d->factor[0].divisor, + d->factor[1].multiplier, d->factor[1].divisor, + e->multiplier, e->divisor, + d->factor[2].multiplier, d->factor[2].divisor); + + master_clock = module_clock * d->factor[2].divisor + / d->factor[2].multiplier; + bus_clock = master_clock * d->factor[1].multiplier + / d->factor[1].divisor; + memory_clock = master_clock * e->multiplier + / e->divisor; + cpu_clock = master_clock * d->factor[0].multiplier + / d->factor[0].divisor; + + current_cpu_data.cpu_clock = cpu_clock; + current_cpu_data.master_clock = master_clock; + current_cpu_data.bus_clock = bus_clock; + current_cpu_data.memory_clock = memory_clock; + current_cpu_data.module_clock = module_clock; +} +#endif + +void __init time_init(void) +{ + unsigned int timer_freq = 0; + unsigned int ifc, pfc, bfc; + unsigned long interval; +#ifdef CONFIG_CPU_SUBTYPE_ST40STB1 + unsigned long pvr; + unsigned short frqcr; +#endif + + if (board_time_init) + board_time_init(); + + /* + * If we don't have an RTC (such as with the SH7300), don't attempt to + * probe the timer frequency. Rely on an either hardcoded peripheral + * clock value, or on the sh_pclk command line option. Note that we + * still need to have CONFIG_SH_PCLK_FREQ set in order for things like + * CLOCK_TICK_RATE to be sane. + */ + current_cpu_data.module_clock = sh_pclk_freq; + +#ifdef CONFIG_SH_PCLK_CALC + /* XXX: Switch this over to a more generic test. */ + { + unsigned int freq; + + /* + * If we've specified a peripheral clock frequency, and we have + * an RTC, compare it against the autodetected value. Complain + * if there's a mismatch. + */ + timer_freq = get_timer_frequency(); + freq = timer_freq * 4; + + if (sh_pclk_freq && (sh_pclk_freq/100*99 > freq || sh_pclk_freq/100*101 < freq)) { + printk(KERN_NOTICE "Calculated peripheral clock value " + "%d differs from sh_pclk value %d, fixing..\n", + freq, sh_pclk_freq); + current_cpu_data.module_clock = freq; + } + } +#endif + +#ifdef CONFIG_CPU_SUBTYPE_ST40STB1 + /* XXX: Update ST40 code to use board_time_init() */ + pvr = ctrl_inl(CCN_PVR); + frqcr = ctrl_inw(FRQCR); + printk("time.c ST40 Probe: PVR %08lx, FRQCR %04hx\n", pvr, frqcr); + + if (((pvr >> CCN_PVR_CHIP_SHIFT) & CCN_PVR_CHIP_MASK) == CCN_PVR_CHIP_ST40STB1) + st40_specific_time_init(current_cpu_data.module_clock, frqcr); + else +#endif + get_current_frequency_divisors(&ifc, &bfc, &pfc); + + if (rtc_get_time) { + rtc_get_time(&xtime); + } else { + xtime.tv_sec = mktime(2000, 1, 1, 0, 0, 0); + xtime.tv_nsec = 0; + } + + set_normalized_timespec(&wall_to_monotonic, + -xtime.tv_sec, -xtime.tv_nsec); + + if (board_timer_setup) { + board_timer_setup(&irq0); + } else { + setup_irq(TIMER_IRQ, &irq0); + } + + /* + * for ST40 chips the current_cpu_data should already be set + * so not having valid pfc/bfc/ifc shouldn't be a problem + */ + if (!current_cpu_data.master_clock) + current_cpu_data.master_clock = current_cpu_data.module_clock * pfc; + if (!current_cpu_data.bus_clock) + current_cpu_data.bus_clock = current_cpu_data.master_clock / bfc; + if (!current_cpu_data.cpu_clock) + current_cpu_data.cpu_clock = current_cpu_data.master_clock / ifc; + + printk("CPU clock: %d.%02dMHz\n", + (current_cpu_data.cpu_clock / 1000000), + (current_cpu_data.cpu_clock % 1000000)/10000); + printk("Bus clock: %d.%02dMHz\n", + (current_cpu_data.bus_clock / 1000000), + (current_cpu_data.bus_clock % 1000000)/10000); +#ifdef CONFIG_CPU_SUBTYPE_ST40STB1 + printk("Memory clock: %d.%02dMHz\n", + (current_cpu_data.memory_clock / 1000000), + (current_cpu_data.memory_clock % 1000000)/10000); +#endif + printk("Module clock: %d.%02dMHz\n", + (current_cpu_data.module_clock / 1000000), + (current_cpu_data.module_clock % 1000000)/10000); + + interval = (current_cpu_data.module_clock/4 + HZ/2) / HZ; + + printk("Interval = %ld\n", interval); + + /* Start TMU0 */ + ctrl_outb(0, TMU_TSTR); +#if !defined(CONFIG_CPU_SUBTYPE_SH7300) + ctrl_outb(TMU_TOCR_INIT, TMU_TOCR); +#endif + ctrl_outw(TMU0_TCR_INIT, TMU0_TCR); + ctrl_outl(interval, TMU0_TCOR); + ctrl_outl(interval, TMU0_TCNT); + ctrl_outb(TMU_TSTR_INIT, TMU_TSTR); + +#if defined(CONFIG_SH_KGDB) + /* + * Set up kgdb as requested. We do it here because the serial + * init uses the timer vars we just set up for figuring baud. + */ + kgdb_init(); +#endif +} diff --git a/arch/sh/kernel/traps.c b/arch/sh/kernel/traps.c new file mode 100644 index 000000000000..7eb06719d844 --- /dev/null +++ b/arch/sh/kernel/traps.c @@ -0,0 +1,712 @@ +/* $Id: traps.c,v 1.17 2004/05/02 01:46:30 sugioka Exp $ + * + * linux/arch/sh/traps.c + * + * SuperH version: Copyright (C) 1999 Niibe Yutaka + * Copyright (C) 2000 Philipp Rumpf + * Copyright (C) 2000 David Howells + * Copyright (C) 2002, 2003 Paul Mundt + */ + +/* + * 'Traps.c' handles hardware traps and faults after we have saved some + * state in 'entry.S'. + */ +#include <linux/config.h> +#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/module.h> +#include <linux/kallsyms.h> + +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/atomic.h> +#include <asm/processor.h> +#include <asm/sections.h> + +#ifdef CONFIG_SH_KGDB +#include <asm/kgdb.h> +#define CHK_REMOTE_DEBUG(regs) \ +{ \ + if ((kgdb_debug_hook != (kgdb_debug_hook_t *) NULL) && (!user_mode(regs))) \ + { \ + (*kgdb_debug_hook)(regs); \ + } \ +} +#else +#define CHK_REMOTE_DEBUG(regs) +#endif + +#define DO_ERROR(trapnr, signr, str, name, tsk) \ +asmlinkage void do_##name(unsigned long r4, unsigned long r5, \ + unsigned long r6, unsigned long r7, \ + struct pt_regs regs) \ +{ \ + unsigned long error_code; \ + \ + /* Check if it's a DSP instruction */ \ + if (is_dsp_inst(®s)) { \ + /* Enable DSP mode, and restart instruction. */ \ + regs.sr |= SR_DSP; \ + return; \ + } \ + \ + asm volatile("stc r2_bank, %0": "=r" (error_code)); \ + local_irq_enable(); \ + tsk->thread.error_code = error_code; \ + tsk->thread.trap_no = trapnr; \ + CHK_REMOTE_DEBUG(®s); \ + force_sig(signr, tsk); \ + die_if_no_fixup(str,®s,error_code); \ +} + +#ifdef CONFIG_CPU_SH2 +#define TRAP_RESERVED_INST 4 +#define TRAP_ILLEGAL_SLOT_INST 6 +#else +#define TRAP_RESERVED_INST 12 +#define TRAP_ILLEGAL_SLOT_INST 13 +#endif + +/* + * These constants are for searching for possible module text + * segments. VMALLOC_OFFSET comes from mm/vmalloc.c; MODULE_RANGE is + * a guess of how much space is likely to be vmalloced. + */ +#define VMALLOC_OFFSET (8*1024*1024) +#define MODULE_RANGE (8*1024*1024) + +spinlock_t die_lock; + +void die(const char * str, struct pt_regs * regs, long err) +{ + static int die_counter; + + console_verbose(); + spin_lock_irq(&die_lock); + printk("%s: %04lx [#%d]\n", str, err & 0xffff, ++die_counter); + CHK_REMOTE_DEBUG(regs); + 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 int handle_unaligned_notify_count = 10; + +/* + * try and fix up kernelspace address errors + * - userspace errors just cause EFAULT to be returned, resulting in SEGV + * - kernel/userspace interfaces cause a jump to an appropriate handler + * - other kernel errors are bad + * - return 0 if fixed-up, -EFAULT if non-fatal (to the kernel) fault + */ +static int 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 0; + } + die(str, regs, err); + } + return -EFAULT; +} + +/* + * handle an instruction that does an unaligned memory access by emulating the + * desired behaviour + * - note that PC _may not_ point to the faulting instruction + * (if that instruction is in a branch delay slot) + * - return 0 if emulation okay, -EFAULT on existential error + */ +static int handle_unaligned_ins(u16 instruction, struct pt_regs *regs) +{ + int ret, index, count; + unsigned long *rm, *rn; + unsigned char *src, *dst; + + index = (instruction>>8)&15; /* 0x0F00 */ + rn = ®s->regs[index]; + + index = (instruction>>4)&15; /* 0x00F0 */ + rm = ®s->regs[index]; + + count = 1<<(instruction&3); + + ret = -EFAULT; + switch (instruction>>12) { + case 0: /* mov.[bwl] to/from memory via r0+rn */ + if (instruction & 8) { + /* from memory */ + src = (unsigned char*) *rm; + src += regs->regs[0]; + dst = (unsigned char*) rn; + *(unsigned long*)dst = 0; + +#ifdef __LITTLE_ENDIAN__ + if (copy_from_user(dst, src, count)) + goto fetch_fault; + + if ((count == 2) && dst[1] & 0x80) { + dst[2] = 0xff; + dst[3] = 0xff; + } +#else + dst += 4-count; + + if (__copy_user(dst, src, count)) + goto fetch_fault; + + if ((count == 2) && dst[2] & 0x80) { + dst[0] = 0xff; + dst[1] = 0xff; + } +#endif + } else { + /* to memory */ + src = (unsigned char*) rm; +#if !defined(__LITTLE_ENDIAN__) + src += 4-count; +#endif + dst = (unsigned char*) *rn; + dst += regs->regs[0]; + + if (copy_to_user(dst, src, count)) + goto fetch_fault; + } + ret = 0; + break; + + case 1: /* mov.l Rm,@(disp,Rn) */ + src = (unsigned char*) rm; + dst = (unsigned char*) *rn; + dst += (instruction&0x000F)<<2; + + if (copy_to_user(dst,src,4)) + goto fetch_fault; + ret = 0; + break; + + case 2: /* mov.[bwl] to memory, possibly with pre-decrement */ + if (instruction & 4) + *rn -= count; + src = (unsigned char*) rm; + dst = (unsigned char*) *rn; +#if !defined(__LITTLE_ENDIAN__) + src += 4-count; +#endif + if (copy_to_user(dst, src, count)) + goto fetch_fault; + ret = 0; + break; + + case 5: /* mov.l @(disp,Rm),Rn */ + src = (unsigned char*) *rm; + src += (instruction&0x000F)<<2; + dst = (unsigned char*) rn; + *(unsigned long*)dst = 0; + + if (copy_from_user(dst,src,4)) + goto fetch_fault; + ret = 0; + break; + + case 6: /* mov.[bwl] from memory, possibly with post-increment */ + src = (unsigned char*) *rm; + if (instruction & 4) + *rm += count; + dst = (unsigned char*) rn; + *(unsigned long*)dst = 0; + +#ifdef __LITTLE_ENDIAN__ + if (copy_from_user(dst, src, count)) + goto fetch_fault; + + if ((count == 2) && dst[1] & 0x80) { + dst[2] = 0xff; + dst[3] = 0xff; + } +#else + dst += 4-count; + + if (copy_from_user(dst, src, count)) + goto fetch_fault; + + if ((count == 2) && dst[2] & 0x80) { + dst[0] = 0xff; + dst[1] = 0xff; + } +#endif + ret = 0; + break; + + case 8: + switch ((instruction&0xFF00)>>8) { + case 0x81: /* mov.w R0,@(disp,Rn) */ + src = (unsigned char*) ®s->regs[0]; +#if !defined(__LITTLE_ENDIAN__) + src += 2; +#endif + dst = (unsigned char*) *rm; /* called Rn in the spec */ + dst += (instruction&0x000F)<<1; + + if (copy_to_user(dst, src, 2)) + goto fetch_fault; + ret = 0; + break; + + case 0x85: /* mov.w @(disp,Rm),R0 */ + src = (unsigned char*) *rm; + src += (instruction&0x000F)<<1; + dst = (unsigned char*) ®s->regs[0]; + *(unsigned long*)dst = 0; + +#if !defined(__LITTLE_ENDIAN__) + dst += 2; +#endif + + if (copy_from_user(dst, src, 2)) + goto fetch_fault; + +#ifdef __LITTLE_ENDIAN__ + if (dst[1] & 0x80) { + dst[2] = 0xff; + dst[3] = 0xff; + } +#else + if (dst[2] & 0x80) { + dst[0] = 0xff; + dst[1] = 0xff; + } +#endif + ret = 0; + break; + } + break; + } + return ret; + + fetch_fault: + /* Argh. Address not only misaligned but also non-existent. + * Raise an EFAULT and see if it's trapped + */ + return die_if_no_fixup("Fault in unaligned fixup", regs, 0); +} + +/* + * emulate the instruction in the delay slot + * - fetches the instruction from PC+2 + */ +static inline int handle_unaligned_delayslot(struct pt_regs *regs) +{ + u16 instruction; + + if (copy_from_user(&instruction, (u16 *)(regs->pc+2), 2)) { + /* the instruction-fetch faulted */ + if (user_mode(regs)) + return -EFAULT; + + /* kernel */ + die("delay-slot-insn faulting in handle_unaligned_delayslot", regs, 0); + } + + return handle_unaligned_ins(instruction,regs); +} + +/* + * handle an instruction that does an unaligned memory access + * - have to be careful of branch delay-slot instructions that fault + * SH3: + * - if the branch would be taken PC points to the branch + * - if the branch would not be taken, PC points to delay-slot + * SH4: + * - PC always points to delayed branch + * - return 0 if handled, -EFAULT if failed (may not return if in kernel) + */ + +/* Macros to determine offset from current PC for branch instructions */ +/* Explicit type coercion is used to force sign extension where needed */ +#define SH_PC_8BIT_OFFSET(instr) ((((signed char)(instr))*2) + 4) +#define SH_PC_12BIT_OFFSET(instr) ((((signed short)(instr<<4))>>3) + 4) + +static int handle_unaligned_access(u16 instruction, struct pt_regs *regs) +{ + u_int rm; + int ret, index; + + index = (instruction>>8)&15; /* 0x0F00 */ + rm = regs->regs[index]; + + /* shout about the first ten userspace fixups */ + if (user_mode(regs) && handle_unaligned_notify_count>0) { + handle_unaligned_notify_count--; + + printk("Fixing up unaligned userspace access in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n", + current->comm,current->pid,(u16*)regs->pc,instruction); + } + + ret = -EFAULT; + switch (instruction&0xF000) { + case 0x0000: + if (instruction==0x000B) { + /* rts */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) + regs->pc = regs->pr; + } + else if ((instruction&0x00FF)==0x0023) { + /* braf @Rm */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) + regs->pc += rm + 4; + } + else if ((instruction&0x00FF)==0x0003) { + /* bsrf @Rm */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) { + regs->pr = regs->pc + 4; + regs->pc += rm + 4; + } + } + else { + /* mov.[bwl] to/from memory via r0+rn */ + goto simple; + } + break; + + case 0x1000: /* mov.l Rm,@(disp,Rn) */ + goto simple; + + case 0x2000: /* mov.[bwl] to memory, possibly with pre-decrement */ + goto simple; + + case 0x4000: + if ((instruction&0x00FF)==0x002B) { + /* jmp @Rm */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) + regs->pc = rm; + } + else if ((instruction&0x00FF)==0x000B) { + /* jsr @Rm */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) { + regs->pr = regs->pc + 4; + regs->pc = rm; + } + } + else { + /* mov.[bwl] to/from memory via r0+rn */ + goto simple; + } + break; + + case 0x5000: /* mov.l @(disp,Rm),Rn */ + goto simple; + + case 0x6000: /* mov.[bwl] from memory, possibly with post-increment */ + goto simple; + + case 0x8000: /* bf lab, bf/s lab, bt lab, bt/s lab */ + switch (instruction&0x0F00) { + case 0x0100: /* mov.w R0,@(disp,Rm) */ + goto simple; + case 0x0500: /* mov.w @(disp,Rm),R0 */ + goto simple; + case 0x0B00: /* bf lab - no delayslot*/ + break; + case 0x0F00: /* bf/s lab */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) { +#if defined(CONFIG_CPU_SH4) || defined(CONFIG_SH7705_CACHE_32KB) + if ((regs->sr & 0x00000001) != 0) + regs->pc += 4; /* next after slot */ + else +#endif + regs->pc += SH_PC_8BIT_OFFSET(instruction); + } + break; + case 0x0900: /* bt lab - no delayslot */ + break; + case 0x0D00: /* bt/s lab */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) { +#if defined(CONFIG_CPU_SH4) || defined(CONFIG_SH7705_CACHE_32KB) + if ((regs->sr & 0x00000001) == 0) + regs->pc += 4; /* next after slot */ + else +#endif + regs->pc += SH_PC_8BIT_OFFSET(instruction); + } + break; + } + break; + + case 0xA000: /* bra label */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) + regs->pc += SH_PC_12BIT_OFFSET(instruction); + break; + + case 0xB000: /* bsr label */ + ret = handle_unaligned_delayslot(regs); + if (ret==0) { + regs->pr = regs->pc + 4; + regs->pc += SH_PC_12BIT_OFFSET(instruction); + } + break; + } + return ret; + + /* handle non-delay-slot instruction */ + simple: + ret = handle_unaligned_ins(instruction,regs); + if (ret==0) + regs->pc += 2; + return ret; +} + +/* + * Handle various address error exceptions + */ +asmlinkage void do_address_error(struct pt_regs *regs, + unsigned long writeaccess, + unsigned long address) +{ + unsigned long error_code; + mm_segment_t oldfs; + u16 instruction; + int tmp; + + asm volatile("stc r2_bank,%0": "=r" (error_code)); + + oldfs = get_fs(); + + if (user_mode(regs)) { + local_irq_enable(); + current->thread.error_code = error_code; + current->thread.trap_no = (writeaccess) ? 8 : 7; + + /* bad PC is not something we can fix */ + if (regs->pc & 1) + goto uspace_segv; + + set_fs(USER_DS); + if (copy_from_user(&instruction, (u16 *)(regs->pc), 2)) { + /* Argh. Fault on the instruction itself. + This should never happen non-SMP + */ + set_fs(oldfs); + goto uspace_segv; + } + + tmp = handle_unaligned_access(instruction, regs); + set_fs(oldfs); + + if (tmp==0) + return; /* sorted */ + + uspace_segv: + printk(KERN_NOTICE "Killing process \"%s\" due to unaligned access\n", current->comm); + force_sig(SIGSEGV, current); + } else { + if (regs->pc & 1) + die("unaligned program counter", regs, error_code); + + set_fs(KERNEL_DS); + if (copy_from_user(&instruction, (u16 *)(regs->pc), 2)) { + /* Argh. Fault on the instruction itself. + This should never happen non-SMP + */ + set_fs(oldfs); + die("insn faulting in do_address_error", regs, 0); + } + + handle_unaligned_access(instruction, regs); + set_fs(oldfs); + } +} + +#ifdef CONFIG_SH_DSP +/* + * SH-DSP support gerg@snapgear.com. + */ +int is_dsp_inst(struct pt_regs *regs) +{ + unsigned short inst; + + /* + * Safe guard if DSP mode is already enabled or we're lacking + * the DSP altogether. + */ + if (!(cpu_data->flags & CPU_HAS_DSP) || (regs->sr & SR_DSP)) + return 0; + + get_user(inst, ((unsigned short *) regs->pc)); + + inst &= 0xf000; + + /* Check for any type of DSP or support instruction */ + if ((inst == 0xf000) || (inst == 0x4000)) + return 1; + + return 0; +} +#else +#define is_dsp_inst(regs) (0) +#endif /* CONFIG_SH_DSP */ + +DO_ERROR(TRAP_RESERVED_INST, SIGILL, "reserved instruction", reserved_inst, current) +DO_ERROR(TRAP_ILLEGAL_SLOT_INST, SIGILL, "illegal slot instruction", illegal_slot_inst, current) + +asmlinkage void do_exception_error(unsigned long r4, unsigned long r5, + unsigned long r6, unsigned long r7, + struct pt_regs regs) +{ + long ex; + asm volatile("stc r2_bank, %0" : "=r" (ex)); + die_if_kernel("exception", ®s, ex); +} + +#if defined(CONFIG_SH_STANDARD_BIOS) +void *gdb_vbr_vector; + +static inline void __init gdb_vbr_init(void) +{ + register unsigned long vbr; + + /* + * Read the old value of the VBR register to initialise + * the vector through which debug and BIOS traps are + * delegated by the Linux trap handler. + */ + asm volatile("stc vbr, %0" : "=r" (vbr)); + + gdb_vbr_vector = (void *)(vbr + 0x100); + printk("Setting GDB trap vector to 0x%08lx\n", + (unsigned long)gdb_vbr_vector); +} +#endif + +void __init per_cpu_trap_init(void) +{ + extern void *vbr_base; + +#ifdef CONFIG_SH_STANDARD_BIOS + gdb_vbr_init(); +#endif + + /* NOTE: The VBR value should be at P1 + (or P2, virtural "fixed" address space). + It's definitely should not in physical address. */ + + asm volatile("ldc %0, vbr" + : /* no output */ + : "r" (&vbr_base) + : "memory"); +} + +void __init trap_init(void) +{ + extern void *exception_handling_table[]; + + exception_handling_table[TRAP_RESERVED_INST] + = (void *)do_reserved_inst; + exception_handling_table[TRAP_ILLEGAL_SLOT_INST] + = (void *)do_illegal_slot_inst; + +#ifdef CONFIG_CPU_SH4 + if (!(cpu_data->flags & CPU_HAS_FPU)) { + /* For SH-4 lacking an FPU, treat floating point instructions + as reserved. */ + /* entry 64 corresponds to EXPEVT=0x800 */ + exception_handling_table[64] = (void *)do_reserved_inst; + exception_handling_table[65] = (void *)do_illegal_slot_inst; + } +#endif + + /* Setup VBR for boot cpu */ + per_cpu_trap_init(); +} + +void show_stack(struct task_struct *tsk, unsigned long *sp) +{ + unsigned long *stack, addr; + unsigned long module_start = VMALLOC_START; + unsigned long module_end = VMALLOC_END; + int i = 1; + + if (tsk && !sp) { + sp = (unsigned long *)tsk->thread.sp; + } + + if (!sp) { + __asm__ __volatile__ ( + "mov r15, %0\n\t" + "stc r7_bank, %1\n\t" + : "=r" (module_start), + "=r" (module_end) + ); + + sp = (unsigned long *)module_start; + } + + stack = sp; + + printk("\nCall trace: "); +#ifdef CONFIG_KALLSYMS + printk("\n"); +#endif + + while (!kstack_end(stack)) { + addr = *stack++; + if (((addr >= (unsigned long)_text) && + (addr <= (unsigned long)_etext)) || + ((addr >= module_start) && (addr <= module_end))) { + /* + * For 80-columns display, 6 entry is maximum. + * NOTE: '[<8c00abcd>] ' consumes 13 columns . + */ +#ifndef CONFIG_KALLSYMS + if (i && ((i % 6) == 0)) + printk("\n "); +#endif + printk("[<%08lx>] ", addr); + print_symbol("%s\n", addr); + i++; + } + } + + printk("\n"); +} + +void show_task(unsigned long *sp) +{ + show_stack(NULL, sp); +} + +void dump_stack(void) +{ + show_stack(NULL, NULL); +} +EXPORT_SYMBOL(dump_stack); diff --git a/arch/sh/kernel/vmlinux.lds.S b/arch/sh/kernel/vmlinux.lds.S new file mode 100644 index 000000000000..51bdc1cf7838 --- /dev/null +++ b/arch/sh/kernel/vmlinux.lds.S @@ -0,0 +1,155 @@ +/* $Id: vmlinux.lds.S,v 1.8 2003/05/16 17:18:14 lethal Exp $ + * ld script to make SuperH Linux kernel + * Written by Niibe Yutaka + */ +#include <linux/config.h> +#include <asm-generic/vmlinux.lds.h> + +#ifdef CONFIG_CPU_LITTLE_ENDIAN +OUTPUT_FORMAT("elf32-sh-linux", "elf32-sh-linux", "elf32-sh-linux") +#else +OUTPUT_FORMAT("elf32-shbig-linux", "elf32-shbig-linux", "elf32-shbig-linux") +#endif +OUTPUT_ARCH(sh) +ENTRY(_start) +SECTIONS +{ + . = 0x80000000 + CONFIG_MEMORY_START + CONFIG_ZERO_PAGE_OFFSET; + _text = .; /* Text and read-only data */ + text = .; /* Text and read-only data */ + .empty_zero_page : { + *(.empty_zero_page) + } = 0 + .text : { + *(.text) + SCHED_TEXT + LOCK_TEXT + *(.fixup) + *(.gnu.warning) + } = 0x0009 + + . = ALIGN(16); /* Exception table */ + __start___ex_table = .; + __ex_table : { *(__ex_table) } + __stop___ex_table = .; + + RODATA + + _etext = .; /* End of text section */ + + .data : { /* Data */ + *(.data) + + /* Align the initial ramdisk image (INITRD) on page boundaries. */ + . = ALIGN(4096); + __rd_start = .; + *(.initrd) + . = ALIGN(4096); + __rd_end = .; + + CONSTRUCTORS + } + + . = ALIGN(4096); + .data.page_aligned : { *(.data.idt) } + + . = ALIGN(32); + __per_cpu_start = .; + .data.percpu : { *(.data.percpu) } + __per_cpu_end = .; + .data.cacheline_aligned : { *(.data.cacheline_aligned) } + + _edata = .; /* End of data section */ + + . = ALIGN(8192); /* init_task */ + .data.init_task : { *(.data.init_task) } + /* stack */ + .stack : { stack = .; _stack = .; } + + . = ALIGN(4096); /* Init code and data */ + __init_begin = .; + _sinittext = .; + .init.text : { *(.init.text) } + _einittext = .; + .init.data : { *(.init.data) } + . = ALIGN(16); + __setup_start = .; + .init.setup : { *(.init.setup) } + __setup_end = .; + __initcall_start = .; + .initcall.init : { + *(.initcall1.init) + *(.initcall2.init) + *(.initcall3.init) + *(.initcall4.init) + *(.initcall5.init) + *(.initcall6.init) + *(.initcall7.init) + } + __initcall_end = .; + __con_initcall_start = .; + .con_initcall.init : { *(.con_initcall.init) } + __con_initcall_end = .; + SECURITY_INIT + __initramfs_start = .; + .init.ramfs : { *(.init.ramfs) } + __initramfs_end = .; + __machvec_start = .; + .init.machvec : { *(.init.machvec) } + __machvec_end = .; + . = ALIGN(4096); + __init_end = .; + + . = ALIGN(4); + __bss_start = .; /* BSS */ + .bss : { *(.bss) } + + . = ALIGN(4); + _end = . ; + + /* When something in the kernel is NOT compiled as a module, the + * module cleanup code and data are put into these segments. Both + * can then be thrown away, as cleanup code is never called unless + * it's a module. + */ + /DISCARD/ : { + *(.exit.text) + *(.exit.data) + *(.exitcall.exit) + } + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.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 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} |