diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /arch/cris/arch-v10/kernel |
Initial import
Diffstat (limited to 'arch/cris/arch-v10/kernel')
-rw-r--r-- | arch/cris/arch-v10/kernel/Makefile | 17 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/crisksyms.c | 16 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/debugport.c | 559 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/dma.c | 287 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/entry.S | 969 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/fasttimer.c | 834 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/head.S | 723 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/io_interface_mux.c | 1182 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/irq.c | 235 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/kgdb.c | 1144 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/process.c | 193 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/ptrace.c | 202 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/setup.c | 106 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/shadows.c | 36 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/signal.c | 438 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/time.c | 267 | ||||
-rw-r--r-- | arch/cris/arch-v10/kernel/traps.c | 131 |
17 files changed, 7339 insertions, 0 deletions
diff --git a/arch/cris/arch-v10/kernel/Makefile b/arch/cris/arch-v10/kernel/Makefile new file mode 100644 index 000000000..4841e822c --- /dev/null +++ b/arch/cris/arch-v10/kernel/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for the linux kernel. +# + +extra-y := head.o + + +obj-y := entry.o traps.o shadows.o debugport.o irq.o \ + process.o setup.o signal.o traps.o time.o ptrace.o \ + dma.o io_interface_mux.o + +obj-$(CONFIG_ETRAX_KGDB) += kgdb.o +obj-$(CONFIG_ETRAX_FAST_TIMER) += fasttimer.o +obj-$(CONFIG_MODULES) += crisksyms.o + +clean: + diff --git a/arch/cris/arch-v10/kernel/crisksyms.c b/arch/cris/arch-v10/kernel/crisksyms.c new file mode 100644 index 000000000..1ca6fc283 --- /dev/null +++ b/arch/cris/arch-v10/kernel/crisksyms.c @@ -0,0 +1,16 @@ +#include <linux/module.h> +#include <asm/io.h> +#include <arch/svinto.h> + +/* Export shadow registers for the CPU I/O pins */ +EXPORT_SYMBOL(genconfig_shadow); +EXPORT_SYMBOL(port_pa_data_shadow); +EXPORT_SYMBOL(port_pa_dir_shadow); +EXPORT_SYMBOL(port_pb_data_shadow); +EXPORT_SYMBOL(port_pb_dir_shadow); +EXPORT_SYMBOL(port_pb_config_shadow); +EXPORT_SYMBOL(port_g_data_shadow); + +/* Cache flush functions */ +EXPORT_SYMBOL(flush_etrax_cache); +EXPORT_SYMBOL(prepare_rx_descriptor); diff --git a/arch/cris/arch-v10/kernel/debugport.c b/arch/cris/arch-v10/kernel/debugport.c new file mode 100644 index 000000000..7d307cce8 --- /dev/null +++ b/arch/cris/arch-v10/kernel/debugport.c @@ -0,0 +1,559 @@ +/* Serialport functions for debugging + * + * Copyright (c) 2000-2007 Axis Communications AB + * + * Authors: Bjorn Wesen + * + * Exports: + * console_print_etrax(char *buf) + * int getDebugChar() + * putDebugChar(int) + * enableDebugIRQ() + * init_etrax_debug() + * + */ + +#include <linux/console.h> +#include <linux/init.h> +#include <linux/major.h> +#include <linux/delay.h> +#include <linux/tty.h> +#include <arch/svinto.h> + +extern void reset_watchdog(void); + +struct dbg_port +{ + unsigned int index; + const volatile unsigned* read; + volatile char* write; + volatile unsigned* xoff; + volatile char* baud; + volatile char* tr_ctrl; + volatile char* rec_ctrl; + unsigned long irq; + unsigned int started; + unsigned long baudrate; + unsigned char parity; + unsigned int bits; +}; + +struct dbg_port ports[]= +{ + { + 0, + R_SERIAL0_READ, + R_SERIAL0_TR_DATA, + R_SERIAL0_XOFF, + R_SERIAL0_BAUD, + R_SERIAL0_TR_CTRL, + R_SERIAL0_REC_CTRL, + IO_STATE(R_IRQ_MASK1_SET, ser0_data, set), + 0, + 115200, + 'N', + 8 + }, + { + 1, + R_SERIAL1_READ, + R_SERIAL1_TR_DATA, + R_SERIAL1_XOFF, + R_SERIAL1_BAUD, + R_SERIAL1_TR_CTRL, + R_SERIAL1_REC_CTRL, + IO_STATE(R_IRQ_MASK1_SET, ser1_data, set), + 0, + 115200, + 'N', + 8 + }, + { + 2, + R_SERIAL2_READ, + R_SERIAL2_TR_DATA, + R_SERIAL2_XOFF, + R_SERIAL2_BAUD, + R_SERIAL2_TR_CTRL, + R_SERIAL2_REC_CTRL, + IO_STATE(R_IRQ_MASK1_SET, ser2_data, set), + 0, + 115200, + 'N', + 8 + }, + { + 3, + R_SERIAL3_READ, + R_SERIAL3_TR_DATA, + R_SERIAL3_XOFF, + R_SERIAL3_BAUD, + R_SERIAL3_TR_CTRL, + R_SERIAL3_REC_CTRL, + IO_STATE(R_IRQ_MASK1_SET, ser3_data, set), + 0, + 115200, + 'N', + 8 + } +}; + +#ifdef CONFIG_ETRAX_SERIAL +extern struct tty_driver *serial_driver; +#endif + +struct dbg_port* port = +#if defined(CONFIG_ETRAX_DEBUG_PORT0) + &ports[0]; +#elif defined(CONFIG_ETRAX_DEBUG_PORT1) + &ports[1]; +#elif defined(CONFIG_ETRAX_DEBUG_PORT2) + &ports[2]; +#elif defined(CONFIG_ETRAX_DEBUG_PORT3) + &ports[3]; +#else + NULL; +#endif + +static struct dbg_port* kgdb_port = +#if defined(CONFIG_ETRAX_KGDB_PORT0) + &ports[0]; +#elif defined(CONFIG_ETRAX_KGDB_PORT1) + &ports[1]; +#elif defined(CONFIG_ETRAX_KGDB_PORT2) + &ports[2]; +#elif defined(CONFIG_ETRAX_KGDB_PORT3) + &ports[3]; +#else + NULL; +#endif + +static void +start_port(struct dbg_port* p) +{ + unsigned long rec_ctrl = 0; + unsigned long tr_ctrl = 0; + + if (!p) + return; + + if (p->started) + return; + p->started = 1; + + if (p->index == 0) + { + genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG, dma6); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma6, unused); + } + else if (p->index == 1) + { + genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG, dma8); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma8, usb); + } + else if (p->index == 2) + { + genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG, dma2); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma2, par0); + genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG, dma3); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma3, par0); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, ser2, select); + } + else + { + genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG, dma4); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma4, par1); + genconfig_shadow &= ~IO_MASK(R_GEN_CONFIG, dma5); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, dma5, par1); + genconfig_shadow |= IO_STATE(R_GEN_CONFIG, ser3, select); + } + + *R_GEN_CONFIG = genconfig_shadow; + + *p->xoff = + IO_STATE(R_SERIAL0_XOFF, tx_stop, enable) | + IO_STATE(R_SERIAL0_XOFF, auto_xoff, disable) | + IO_FIELD(R_SERIAL0_XOFF, xoff_char, 0); + + switch (p->baudrate) + { + case 0: + case 115200: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c115k2Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c115k2Hz); + break; + case 1200: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c1200Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c1200Hz); + break; + case 2400: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c2400Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c2400Hz); + break; + case 4800: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c4800Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c4800Hz); + break; + case 9600: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c9600Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c9600Hz); + break; + case 19200: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c19k2Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c19k2Hz); + break; + case 38400: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c38k4Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c38k4Hz); + break; + case 57600: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c57k6Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c57k6Hz); + break; + default: + *p->baud = + IO_STATE(R_SERIAL0_BAUD, tr_baud, c115k2Hz) | + IO_STATE(R_SERIAL0_BAUD, rec_baud, c115k2Hz); + break; + } + + if (p->parity == 'E') { + rec_ctrl = + IO_STATE(R_SERIAL0_REC_CTRL, rec_par, even) | + IO_STATE(R_SERIAL0_REC_CTRL, rec_par_en, enable); + tr_ctrl = + IO_STATE(R_SERIAL0_TR_CTRL, tr_par, even) | + IO_STATE(R_SERIAL0_TR_CTRL, tr_par_en, enable); + } else if (p->parity == 'O') { + rec_ctrl = + IO_STATE(R_SERIAL0_REC_CTRL, rec_par, odd) | + IO_STATE(R_SERIAL0_REC_CTRL, rec_par_en, enable); + tr_ctrl = + IO_STATE(R_SERIAL0_TR_CTRL, tr_par, odd) | + IO_STATE(R_SERIAL0_TR_CTRL, tr_par_en, enable); + } else { + rec_ctrl = + IO_STATE(R_SERIAL0_REC_CTRL, rec_par, even) | + IO_STATE(R_SERIAL0_REC_CTRL, rec_par_en, disable); + tr_ctrl = + IO_STATE(R_SERIAL0_TR_CTRL, tr_par, even) | + IO_STATE(R_SERIAL0_TR_CTRL, tr_par_en, disable); + } + if (p->bits == 7) + { + rec_ctrl |= IO_STATE(R_SERIAL0_REC_CTRL, rec_bitnr, rec_7bit); + tr_ctrl |= IO_STATE(R_SERIAL0_TR_CTRL, tr_bitnr, tr_7bit); + } + else + { + rec_ctrl |= IO_STATE(R_SERIAL0_REC_CTRL, rec_bitnr, rec_8bit); + tr_ctrl |= IO_STATE(R_SERIAL0_TR_CTRL, tr_bitnr, tr_8bit); + } + + *p->rec_ctrl = + IO_STATE(R_SERIAL0_REC_CTRL, dma_err, stop) | + IO_STATE(R_SERIAL0_REC_CTRL, rec_enable, enable) | + IO_STATE(R_SERIAL0_REC_CTRL, rts_, active) | + IO_STATE(R_SERIAL0_REC_CTRL, sampling, middle) | + IO_STATE(R_SERIAL0_REC_CTRL, rec_stick_par, normal) | + rec_ctrl; + + *p->tr_ctrl = + IO_FIELD(R_SERIAL0_TR_CTRL, txd, 0) | + IO_STATE(R_SERIAL0_TR_CTRL, tr_enable, enable) | + IO_STATE(R_SERIAL0_TR_CTRL, auto_cts, disabled) | + IO_STATE(R_SERIAL0_TR_CTRL, stop_bits, one_bit) | + IO_STATE(R_SERIAL0_TR_CTRL, tr_stick_par, normal) | + tr_ctrl; +} + +static void +console_write_direct(struct console *co, const char *buf, unsigned int len) +{ + int i; + unsigned long flags; + + if (!port) + return; + + local_irq_save(flags); + + /* Send data */ + for (i = 0; i < len; i++) { + /* LF -> CRLF */ + if (buf[i] == '\n') { + while (!(*port->read & IO_MASK(R_SERIAL0_READ, tr_ready))) + ; + *port->write = '\r'; + } + /* Wait until transmitter is ready and send.*/ + while (!(*port->read & IO_MASK(R_SERIAL0_READ, tr_ready))) + ; + *port->write = buf[i]; + } + + /* + * Feed the watchdog, otherwise it will reset the chip during boot. + * The time to send an ordinary boot message line (10-90 chars) + * varies between 1-8ms at 115200. What makes up for the additional + * 90ms that allows the watchdog to bite? + */ + reset_watchdog(); + + local_irq_restore(flags); +} + +static void +console_write(struct console *co, const char *buf, unsigned int len) +{ + if (!port) + return; + + console_write_direct(co, buf, len); +} + +/* legacy function */ + +void +console_print_etrax(const char *buf) +{ + console_write(NULL, buf, strlen(buf)); +} + +/* Use polling to get a single character FROM the debug port */ + +int +getDebugChar(void) +{ + unsigned long readval; + + if (!kgdb_port) + return 0; + + do { + readval = *kgdb_port->read; + } while (!(readval & IO_MASK(R_SERIAL0_READ, data_avail))); + + return (readval & IO_MASK(R_SERIAL0_READ, data_in)); +} + +/* Use polling to put a single character to the debug port */ + +void +putDebugChar(int val) +{ + if (!kgdb_port) + return; + + while (!(*kgdb_port->read & IO_MASK(R_SERIAL0_READ, tr_ready))) + ; + *kgdb_port->write = val; +} + +/* Enable irq for receiving chars on the debug port, used by kgdb */ + +void +enableDebugIRQ(void) +{ + if (!kgdb_port) + return; + + *R_IRQ_MASK1_SET = kgdb_port->irq; + /* use R_VECT_MASK directly, since we really bypass Linux normal + * IRQ handling in kgdb anyway, we don't need to use enable_irq + */ + *R_VECT_MASK_SET = IO_STATE(R_VECT_MASK_SET, serial, set); + + *kgdb_port->rec_ctrl = IO_STATE(R_SERIAL0_REC_CTRL, rec_enable, enable); +} + +static int __init +console_setup(struct console *co, char *options) +{ + char* s; + + if (options) { + port = &ports[co->index]; + port->baudrate = 115200; + port->parity = 'N'; + port->bits = 8; + port->baudrate = simple_strtoul(options, NULL, 10); + s = options; + while(*s >= '0' && *s <= '9') + s++; + if (*s) port->parity = *s++; + if (*s) port->bits = *s++ - '0'; + port->started = 0; + start_port(0); + } + return 0; +} + + +/* This is a dummy serial device that throws away anything written to it. + * This is used when no debug output is wanted. + */ +static struct tty_driver dummy_driver; + +static int dummy_open(struct tty_struct *tty, struct file * filp) +{ + return 0; +} + +static void dummy_close(struct tty_struct *tty, struct file * filp) +{ +} + +static int dummy_write(struct tty_struct * tty, + const unsigned char *buf, int count) +{ + return count; +} + +static int dummy_write_room(struct tty_struct *tty) +{ + return 8192; +} + +static const struct tty_operations dummy_ops = { + .open = dummy_open, + .close = dummy_close, + .write = dummy_write, + .write_room = dummy_write_room, +}; + +void __init +init_dummy_console(void) +{ + memset(&dummy_driver, 0, sizeof(struct tty_driver)); + dummy_driver.driver_name = "serial"; + dummy_driver.name = "ttyS"; + dummy_driver.major = TTY_MAJOR; + dummy_driver.minor_start = 68; + dummy_driver.num = 1; /* etrax100 has 4 serial ports */ + dummy_driver.type = TTY_DRIVER_TYPE_SERIAL; + dummy_driver.subtype = SERIAL_TYPE_NORMAL; + dummy_driver.init_termios = tty_std_termios; + /* Normally B9600 default... */ + dummy_driver.init_termios.c_cflag = + B115200 | CS8 | CREAD | HUPCL | CLOCAL; + dummy_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + dummy_driver.init_termios.c_ispeed = 115200; + dummy_driver.init_termios.c_ospeed = 115200; + + dummy_driver.ops = &dummy_ops; + if (tty_register_driver(&dummy_driver)) + panic("Couldn't register dummy serial driver\n"); +} + +static struct tty_driver* +etrax_console_device(struct console* co, int *index) +{ + if (port) + *index = port->index; + else + *index = 0; +#ifdef CONFIG_ETRAX_SERIAL + return port ? serial_driver : &dummy_driver; +#else + return &dummy_driver; +#endif +} + +static struct console sercons = { + name : "ttyS", + write: console_write, + read : NULL, + device : etrax_console_device, + unblank : NULL, + setup : console_setup, + flags : CON_PRINTBUFFER, + index : -1, + cflag : 0, + next : NULL +}; +static struct console sercons0 = { + name : "ttyS", + write: console_write, + read : NULL, + device : etrax_console_device, + unblank : NULL, + setup : console_setup, + flags : CON_PRINTBUFFER, + index : 0, + cflag : 0, + next : NULL +}; + +static struct console sercons1 = { + name : "ttyS", + write: console_write, + read : NULL, + device : etrax_console_device, + unblank : NULL, + setup : console_setup, + flags : CON_PRINTBUFFER, + index : 1, + cflag : 0, + next : NULL +}; +static struct console sercons2 = { + name : "ttyS", + write: console_write, + read : NULL, + device : etrax_console_device, + unblank : NULL, + setup : console_setup, + flags : CON_PRINTBUFFER, + index : 2, + cflag : 0, + next : NULL +}; +static struct console sercons3 = { + name : "ttyS", + write: console_write, + read : NULL, + device : etrax_console_device, + unblank : NULL, + setup : console_setup, + flags : CON_PRINTBUFFER, + index : 3, + cflag : 0, + next : NULL +}; +/* + * Register console (for printk's etc) + */ + +int __init +init_etrax_debug(void) +{ + static int first = 1; + + if (!first) { + unregister_console(&sercons); + register_console(&sercons0); + register_console(&sercons1); + register_console(&sercons2); + register_console(&sercons3); + init_dummy_console(); + return 0; + } + + first = 0; + register_console(&sercons); + start_port(port); +#ifdef CONFIG_ETRAX_KGDB + start_port(kgdb_port); +#endif + return 0; +} +__initcall(init_etrax_debug); diff --git a/arch/cris/arch-v10/kernel/dma.c b/arch/cris/arch-v10/kernel/dma.c new file mode 100644 index 000000000..579504735 --- /dev/null +++ b/arch/cris/arch-v10/kernel/dma.c @@ -0,0 +1,287 @@ +/* Wrapper for DMA channel allocator that updates DMA client muxing. + * Copyright 2004-2007, Axis Communications AB + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> + +#include <asm/dma.h> +#include <arch/svinto.h> +#include <arch/system.h> + +/* Macro to access ETRAX 100 registers */ +#define SETS(var, reg, field, val) var = (var & ~IO_MASK_(reg##_, field##_)) | \ + IO_STATE_(reg##_, field##_, _##val) + + +static char used_dma_channels[MAX_DMA_CHANNELS]; +static const char * used_dma_channels_users[MAX_DMA_CHANNELS]; + +int cris_request_dma(unsigned int dmanr, const char * device_id, + unsigned options, enum dma_owner owner) +{ + unsigned long flags; + unsigned long int gens; + int fail = -EINVAL; + + if (dmanr >= MAX_DMA_CHANNELS) { + printk(KERN_CRIT "cris_request_dma: invalid DMA channel %u\n", dmanr); + return -EINVAL; + } + + local_irq_save(flags); + if (used_dma_channels[dmanr]) { + local_irq_restore(flags); + if (options & DMA_VERBOSE_ON_ERROR) { + printk(KERN_CRIT "Failed to request DMA %i for %s, already allocated by %s\n", dmanr, device_id, used_dma_channels_users[dmanr]); + } + if (options & DMA_PANIC_ON_ERROR) { + panic("request_dma error!"); + } + return -EBUSY; + } + + gens = genconfig_shadow; + + switch(owner) + { + case dma_eth: + if ((dmanr != NETWORK_TX_DMA_NBR) && + (dmanr != NETWORK_RX_DMA_NBR)) { + printk(KERN_CRIT "Invalid DMA channel for eth\n"); + goto bail; + } + break; + case dma_ser0: + if (dmanr == SER0_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma6, serial0); + } else if (dmanr == SER0_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma7, serial0); + } else { + printk(KERN_CRIT "Invalid DMA channel for ser0\n"); + goto bail; + } + break; + case dma_ser1: + if (dmanr == SER1_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma8, serial1); + } else if (dmanr == SER1_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma9, serial1); + } else { + printk(KERN_CRIT "Invalid DMA channel for ser1\n"); + goto bail; + } + break; + case dma_ser2: + if (dmanr == SER2_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma2, serial2); + } else if (dmanr == SER2_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma3, serial2); + } else { + printk(KERN_CRIT "Invalid DMA channel for ser2\n"); + goto bail; + } + break; + case dma_ser3: + if (dmanr == SER3_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma4, serial3); + } else if (dmanr == SER3_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma5, serial3); + } else { + printk(KERN_CRIT "Invalid DMA channel for ser3\n"); + goto bail; + } + break; + case dma_ata: + if (dmanr == ATA_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma2, ata); + } else if (dmanr == ATA_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma3, ata); + } else { + printk(KERN_CRIT "Invalid DMA channel for ata\n"); + goto bail; + } + break; + case dma_ext0: + if (dmanr == EXTDMA0_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma4, extdma0); + } else if (dmanr == EXTDMA0_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma5, extdma0); + } else { + printk(KERN_CRIT "Invalid DMA channel for ext0\n"); + goto bail; + } + break; + case dma_ext1: + if (dmanr == EXTDMA1_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma6, extdma1); + } else if (dmanr == EXTDMA1_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma7, extdma1); + } else { + printk(KERN_CRIT "Invalid DMA channel for ext1\n"); + goto bail; + } + break; + case dma_int6: + if (dmanr == MEM2MEM_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma7, intdma6); + } else { + printk(KERN_CRIT "Invalid DMA channel for int6\n"); + goto bail; + } + break; + case dma_int7: + if (dmanr == MEM2MEM_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma6, intdma7); + } else { + printk(KERN_CRIT "Invalid DMA channel for int7\n"); + goto bail; + } + break; + case dma_usb: + if (dmanr == USB_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma8, usb); + } else if (dmanr == USB_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma9, usb); + } else { + printk(KERN_CRIT "Invalid DMA channel for usb\n"); + goto bail; + } + break; + case dma_scsi0: + if (dmanr == SCSI0_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma2, scsi0); + } else if (dmanr == SCSI0_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma3, scsi0); + } else { + printk(KERN_CRIT "Invalid DMA channel for scsi0\n"); + goto bail; + } + break; + case dma_scsi1: + if (dmanr == SCSI1_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma4, scsi1); + } else if (dmanr == SCSI1_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma5, scsi1); + } else { + printk(KERN_CRIT "Invalid DMA channel for scsi1\n"); + goto bail; + } + break; + case dma_par0: + if (dmanr == PAR0_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma2, par0); + } else if (dmanr == PAR0_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma3, par0); + } else { + printk(KERN_CRIT "Invalid DMA channel for par0\n"); + goto bail; + } + break; + case dma_par1: + if (dmanr == PAR1_TX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma4, par1); + } else if (dmanr == PAR1_RX_DMA_NBR) { + SETS(gens, R_GEN_CONFIG, dma5, par1); + } else { + printk(KERN_CRIT "Invalid DMA channel for par1\n"); + goto bail; + } + break; + default: + printk(KERN_CRIT "Invalid DMA owner.\n"); + goto bail; + } + + used_dma_channels[dmanr] = 1; + used_dma_channels_users[dmanr] = device_id; + + { + volatile int i; + genconfig_shadow = gens; + *R_GEN_CONFIG = genconfig_shadow; + /* Wait 12 cycles before doing any DMA command */ + for(i = 6; i > 0; i--) + nop(); + } + fail = 0; + bail: + local_irq_restore(flags); + return fail; +} + +void cris_free_dma(unsigned int dmanr, const char * device_id) +{ + unsigned long flags; + if (dmanr >= MAX_DMA_CHANNELS) { + printk(KERN_CRIT "cris_free_dma: invalid DMA channel %u\n", dmanr); + return; + } + + local_irq_save(flags); + if (!used_dma_channels[dmanr]) { + printk(KERN_CRIT "cris_free_dma: DMA channel %u not allocated\n", dmanr); + } else if (device_id != used_dma_channels_users[dmanr]) { + printk(KERN_CRIT "cris_free_dma: DMA channel %u not allocated by device\n", dmanr); + } else { + switch(dmanr) + { + case 0: + *R_DMA_CH0_CMD = IO_STATE(R_DMA_CH0_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH0_CMD, cmd, *R_DMA_CH0_CMD) == + IO_STATE_VALUE(R_DMA_CH0_CMD, cmd, reset)); + break; + case 1: + *R_DMA_CH1_CMD = IO_STATE(R_DMA_CH1_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH1_CMD, cmd, *R_DMA_CH1_CMD) == + IO_STATE_VALUE(R_DMA_CH1_CMD, cmd, reset)); + break; + case 2: + *R_DMA_CH2_CMD = IO_STATE(R_DMA_CH2_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH2_CMD, cmd, *R_DMA_CH2_CMD) == + IO_STATE_VALUE(R_DMA_CH2_CMD, cmd, reset)); + break; + case 3: + *R_DMA_CH3_CMD = IO_STATE(R_DMA_CH3_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH3_CMD, cmd, *R_DMA_CH3_CMD) == + IO_STATE_VALUE(R_DMA_CH3_CMD, cmd, reset)); + break; + case 4: + *R_DMA_CH4_CMD = IO_STATE(R_DMA_CH4_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH4_CMD, cmd, *R_DMA_CH4_CMD) == + IO_STATE_VALUE(R_DMA_CH4_CMD, cmd, reset)); + break; + case 5: + *R_DMA_CH5_CMD = IO_STATE(R_DMA_CH5_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH5_CMD, cmd, *R_DMA_CH5_CMD) == + IO_STATE_VALUE(R_DMA_CH5_CMD, cmd, reset)); + break; + case 6: + *R_DMA_CH6_CMD = IO_STATE(R_DMA_CH6_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH6_CMD, cmd, *R_DMA_CH6_CMD) == + IO_STATE_VALUE(R_DMA_CH6_CMD, cmd, reset)); + break; + case 7: + *R_DMA_CH7_CMD = IO_STATE(R_DMA_CH7_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH7_CMD, cmd, *R_DMA_CH7_CMD) == + IO_STATE_VALUE(R_DMA_CH7_CMD, cmd, reset)); + break; + case 8: + *R_DMA_CH8_CMD = IO_STATE(R_DMA_CH8_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH8_CMD, cmd, *R_DMA_CH8_CMD) == + IO_STATE_VALUE(R_DMA_CH8_CMD, cmd, reset)); + break; + case 9: + *R_DMA_CH9_CMD = IO_STATE(R_DMA_CH9_CMD, cmd, reset); + while (IO_EXTRACT(R_DMA_CH9_CMD, cmd, *R_DMA_CH9_CMD) == + IO_STATE_VALUE(R_DMA_CH9_CMD, cmd, reset)); + break; + } + used_dma_channels[dmanr] = 0; + } + local_irq_restore(flags); +} + +EXPORT_SYMBOL(cris_request_dma); +EXPORT_SYMBOL(cris_free_dma); diff --git a/arch/cris/arch-v10/kernel/entry.S b/arch/cris/arch-v10/kernel/entry.S new file mode 100644 index 000000000..81570fcd0 --- /dev/null +++ b/arch/cris/arch-v10/kernel/entry.S @@ -0,0 +1,969 @@ +/* + * linux/arch/cris/entry.S + * + * Copyright (C) 2000, 2001, 2002 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + */ + +/* + * entry.S contains the system-call and fault low-level handling routines. + * + * NOTE: This code handles signal-recognition, which happens every time + * after a timer-interrupt and after each system call. + * + * Stack layout in 'ret_from_system_call': + * ptrace needs to have all regs on the stack. + * if the order here is changed, it needs to be + * updated in fork.c:copy_process, signal.c:do_signal, + * ptrace.c and ptrace.h + * + */ + +#include <linux/linkage.h> +#include <linux/sys.h> +#include <asm/unistd.h> +#include <arch/sv_addr_ag.h> +#include <asm/errno.h> +#include <asm/thread_info.h> +#include <asm/asm-offsets.h> +#include <asm/page.h> +#include <asm/pgtable.h> + + ;; functions exported from this file + + .globl system_call + .globl ret_from_intr + .globl ret_from_fork + .globl ret_from_kernel_thread + .globl resume + .globl multiple_interrupt + .globl hwbreakpoint + .globl IRQ1_interrupt + .globl spurious_interrupt + .globl hw_bp_trigs + .globl mmu_bus_fault + .globl do_sigtrap + .globl gdb_handle_breakpoint + .globl sys_call_table + + ;; below are various parts of system_call which are not in the fast-path + +#ifdef CONFIG_PREEMPT + ; Check if preemptive kernel scheduling should be done +_resume_kernel: + di + ; Load current task struct + movs.w -8192, $r0 ; THREAD_SIZE = 8192 + and.d $sp, $r0 + move.d [$r0+TI_preempt_count], $r10 ; Preemption disabled? + bne _Rexit + nop +_need_resched: + move.d [$r0+TI_flags], $r10 + btstq TIF_NEED_RESCHED, $r10 ; Check if need_resched is set + bpl _Rexit + nop + ; Ok, lets's do some preemptive kernel scheduling + jsr preempt_schedule_irq + ; Load new task struct + movs.w -8192, $r0 ; THREAD_SIZE = 8192 + and.d $sp, $r0 + ; One more time (with new task) + ba _need_resched + nop +#else +#define _resume_kernel _Rexit +#endif + + ; Called at exit from fork. schedule_tail must be called to drop + ; spinlock if CONFIG_PREEMPT +ret_from_fork: + jsr schedule_tail + ba ret_from_sys_call + nop + +ret_from_kernel_thread: + jsr schedule_tail + move.d $r2, $r10 ; argument is here + jsr $r1 ; call the payload + moveq 0, $r9 ; no syscall restarts, TYVM... + ba ret_from_sys_call + +ret_from_intr: + ;; check for resched if preemptive kernel or if we're going back to user-mode + ;; this test matches the user_regs(regs) macro + ;; we cannot simply test $dccr, because that does not necessarily + ;; reflect what mode we'll return into. + + move.d [$sp + PT_dccr], $r0; regs->dccr + btstq 8, $r0 ; U-flag + bpl _resume_kernel + ; Note that di below is in delay slot + +_resume_userspace: + di ; so need_resched and sigpending don't change + + movs.w -8192, $r0 ; THREAD_SIZE == 8192 + and.d $sp, $r0 + + move.d [$r0+TI_flags], $r10 ; current->work + and.d _TIF_WORK_MASK, $r10 ; is there any work to be done on return + bne _work_pending + nop + ba _Rexit + nop + + ;; The system_call is called by a BREAK instruction, which works like + ;; an interrupt call but it stores the return PC in BRP instead of IRP. + ;; Since we dont really want to have two epilogues (one for system calls + ;; and one for interrupts) we push the contents of BRP instead of IRP in the + ;; system call prologue, to make it look like an ordinary interrupt on the + ;; stackframe. + ;; + ;; Since we can't have system calls inside interrupts, it should not matter + ;; that we don't stack IRP. + ;; + ;; In r9 we have the wanted syscall number. Arguments come in r10,r11,r12,r13,mof,srp + ;; + ;; This function looks on the _surface_ like spaghetti programming, but it's + ;; really designed so that the fast-path does not force cache-loading of non-used + ;; instructions. Only the non-common cases cause the outlined code to run.. + +system_call: + ;; stack-frame similar to the irq heads, which is reversed in ret_from_sys_call + move $brp,[$sp=$sp-16]; instruction pointer and room for a fake SBFS frame + push $srp + push $dccr + push $mof + subq 14*4, $sp ; make room for r0-r13 + movem $r13, [$sp] ; push r0-r13 + push $r10 ; push orig_r10 + clear.d [$sp=$sp-4] ; frametype == 0, normal stackframe + + movs.w -ENOSYS, $r0 + move.d $r0, [$sp+PT_r10] ; put the default return value in r10 in the frame + + ;; check if this process is syscall-traced + + movs.w -8192, $r0 ; THREAD_SIZE == 8192 + and.d $sp, $r0 + + move.d [$r0+TI_flags], $r0 + btstq TIF_SYSCALL_TRACE, $r0 + bmi _syscall_trace_entry + nop + +_syscall_traced: + + ;; check for sanity in the requested syscall number + + cmpu.w NR_syscalls, $r9 + bcc ret_from_sys_call + lslq 2, $r9 ; multiply by 4, in the delay slot + + ;; as a bonus 7th parameter, we give the location on the stack + ;; of the register structure itself. some syscalls need this. + + push $sp + + ;; the parameter carrying registers r10, r11, r12 and 13 are intact. + ;; the fifth and sixth parameters (if any) was in mof and srp + ;; respectively, and we need to put them on the stack. + + push $srp + push $mof + + jsr [$r9+sys_call_table] ; actually do the system call + addq 3*4, $sp ; pop the mof, srp and regs parameters + move.d $r10, [$sp+PT_r10] ; save the return value + + moveq 1, $r9 ; "parameter" to ret_from_sys_call to show it was a sys call + + ;; fall through into ret_from_sys_call to return + +ret_from_sys_call: + ;; r9 is a parameter - if >=1 we came from a syscall, if 0, from an irq + + ;; get the current task-struct pointer (see top for defs) + + movs.w -8192, $r0 ; THREAD_SIZE == 8192 + and.d $sp, $r0 + + di ; make sure need_resched and sigpending don't change + move.d [$r0+TI_flags],$r1 + and.d _TIF_ALLWORK_MASK, $r1 + bne _syscall_exit_work + nop + +_Rexit: + ;; this epilogue MUST match the prologues in multiple_interrupt, irq.h and ptregs.h + pop $r10 ; frametype + bne _RBFexit ; was not CRIS_FRAME_NORMAL, handle otherwise + addq 4, $sp ; skip orig_r10, in delayslot + movem [$sp+], $r13 ; registers r0-r13 + pop $mof ; multiply overflow register + pop $dccr ; condition codes + pop $srp ; subroutine return pointer + ;; now we have a 4-word SBFS frame which we do not want to restore + ;; using RBF since it was not stacked with SBFS. instead we would like to + ;; just get the PC value to restart it with, and skip the rest of + ;; the frame. + ;; Also notice that it's important to use instructions here that + ;; keep the interrupts disabled (since we've already popped DCCR) + move [$sp=$sp+16], $p8; pop the SBFS frame from the sp + jmpu [$sp-16] ; return through the irp field in the sbfs frame + +_RBFexit: + movem [$sp+], $r13 ; registers r0-r13, in delay slot + pop $mof ; multiply overflow register + pop $dccr ; condition codes + pop $srp ; subroutine return pointer + rbf [$sp+] ; return by popping the CPU status + + ;; We get here after doing a syscall if extra work might need to be done + ;; perform syscall exit tracing if needed + +_syscall_exit_work: + ;; $r0 contains current at this point and irq's are disabled + + move.d [$r0+TI_flags], $r1 + btstq TIF_SYSCALL_TRACE, $r1 + bpl _work_pending + nop + + ei + + move.d $r9, $r1 ; preserve r9 + jsr do_syscall_trace + move.d $r1, $r9 + + ba _resume_userspace + nop + +_work_pending: + move.d [$r0+TI_flags], $r1 + btstq TIF_NEED_RESCHED, $r1 + bpl _work_notifysig ; was neither trace nor sched, must be signal/notify + nop + +_work_resched: + move.d $r9, $r1 ; preserve r9 + jsr schedule + move.d $r1, $r9 + di + + move.d [$r0+TI_flags], $r1 + and.d _TIF_WORK_MASK, $r1; ignore the syscall trace counter + beq _Rexit + nop + btstq TIF_NEED_RESCHED, $r1 + bmi _work_resched ; current->work.need_resched + nop + +_work_notifysig: + ;; deal with pending signals and notify-resume requests + + move.d $r9, $r10 ; do_notify_resume syscall/irq param + move.d $sp, $r11 ; the regs param + move.d $r1, $r12 ; the thread_info_flags parameter + jsr do_notify_resume + + ba _Rexit + nop + + ;; We get here as a sidetrack when we've entered a syscall with the + ;; trace-bit set. We need to call do_syscall_trace and then continue + ;; with the call. + +_syscall_trace_entry: + ;; PT_r10 in the frame contains -ENOSYS as required, at this point + + jsr do_syscall_trace + + ;; now re-enter the syscall code to do the syscall itself + ;; we need to restore $r9 here to contain the wanted syscall, and + ;; the other parameter-bearing registers + + move.d [$sp+PT_r9], $r9 + move.d [$sp+PT_orig_r10], $r10 ; PT_r10 is already filled with -ENOSYS. + move.d [$sp+PT_r11], $r11 + move.d [$sp+PT_r12], $r12 + move.d [$sp+PT_r13], $r13 + move [$sp+PT_mof], $mof + move [$sp+PT_srp], $srp + + ba _syscall_traced + nop + + ;; resume performs the actual task-switching, by switching stack pointers + ;; input arguments: r10 = prev, r11 = next, r12 = thread offset in task struct + ;; returns old current in r10 + ;; + ;; TODO: see the i386 version. The switch_to which calls resume in our version + ;; could really be an inline asm of this. + +resume: + push $srp ; we keep the old/new PC on the stack + add.d $r12, $r10 ; r10 = current tasks tss + move $dccr, [$r10+THREAD_dccr]; save irq enable state + di + + move $usp, [$r10+ THREAD_usp] ; save user-mode stackpointer + + ;; See copy_thread for the reason why register R9 is saved. + subq 10*4, $sp + movem $r9, [$sp] ; save non-scratch registers and R9. + + move.d $sp, [$r10+THREAD_ksp] ; save the kernel stack pointer for the old task + move.d $sp, $r10 ; return last running task in r10 + and.d -8192, $r10 ; get thread_info from stackpointer + move.d [$r10+TI_task], $r10 ; get task + add.d $r12, $r11 ; find the new tasks tss + move.d [$r11+THREAD_ksp], $sp ; switch into the new stackframe by restoring kernel sp + + movem [$sp+], $r9 ; restore non-scratch registers and R9. + + move [$r11+THREAD_usp], $usp ; restore user-mode stackpointer + + move [$r11+THREAD_dccr], $dccr ; restore irq enable status + jump [$sp+] ; restore PC + + ;; This is the MMU bus fault handler. + ;; It needs to stack the CPU status and overall is different + ;; from the other interrupt handlers. + +mmu_bus_fault: + ;; For refills we try to do a quick page table lookup. If it is + ;; a real fault we let the mm subsystem handle it. + + ;; the first longword in the sbfs frame was the interrupted PC + ;; which fits nicely with the "IRP" slot in pt_regs normally used to + ;; contain the return address. used by Oops to print kernel errors. + sbfs [$sp=$sp-16] ; push the internal CPU status + push $dccr + di + subq 2*4, $sp + movem $r1, [$sp] + move.d [R_MMU_CAUSE], $r1 + ;; ETRAX 100LX TR89 bugfix: if the second half of an unaligned + ;; write causes a MMU-fault, it will not be restarted correctly. + ;; This could happen if a write crosses a page-boundary and the + ;; second page is not yet COW'ed or even loaded. The workaround + ;; is to clear the unaligned bit in the CPU status record, so + ;; that the CPU will rerun both the first and second halves of + ;; the instruction. This will not have any sideeffects unless + ;; the first half goes to any device or memory that can't be + ;; written twice, and which is mapped through the MMU. + ;; + ;; We only need to do this for writes. + btstq 8, $r1 ; Write access? + bpl 1f + nop + move.d [$sp+16], $r0 ; Clear unaligned bit in csrinstr + and.d ~(1<<5), $r0 + move.d $r0, [$sp+16] +1: btstq 12, $r1 ; Refill? + bpl 2f + lsrq 24, $r1 ; Get PGD index (bit 24-31) + move.d [current_pgd], $r0 ; PGD for the current process + move.d [$r0+$r1.d], $r0 ; Get PMD + beq 2f + nop + and.w PAGE_MASK, $r0 ; Remove PMD flags + move.d [R_MMU_CAUSE], $r1 + lsrq PAGE_SHIFT, $r1 + and.d 0x7ff, $r1 ; Get PTE index into PGD (bit 13-23) + move.d [$r0+$r1.d], $r1 ; Get PTE + beq 2f + nop + ;; Store in TLB + move.d $r1, [R_TLB_LO] + ;; Return + movem [$sp+], $r1 + pop $dccr + rbf [$sp+] ; return by popping the CPU status + +2: ; PMD or PTE missing, let the mm subsystem fix it up. + movem [$sp+], $r1 + pop $dccr + + ; Ok, not that easy, pass it on to the mm subsystem + ; The MMU status record is now on the stack + push $srp ; make a stackframe similar to pt_regs + push $dccr + push $mof + di + subq 14*4, $sp + movem $r13, [$sp] + push $r10 ; dummy orig_r10 + moveq 1, $r10 + push $r10 ; frametype == 1, BUSFAULT frame type + + move.d $sp, $r10 ; pt_regs argument to handle_mmu_bus_fault + + jsr handle_mmu_bus_fault ; in arch/cris/arch-v10/mm/fault.c + + ;; now we need to return through the normal path, we cannot just + ;; do the RBFexit since we might have killed off the running + ;; process due to a SEGV, scheduled due to a page blocking or + ;; whatever. + + moveq 0, $r9 ; busfault is equivalent to an irq + + ba ret_from_intr + nop + + ;; special handlers for breakpoint and NMI +hwbreakpoint: + push $dccr + di + push $r10 + push $r11 + move.d [hw_bp_trig_ptr],$r10 + move $brp,$r11 + move.d $r11,[$r10+] + move.d $r10,[hw_bp_trig_ptr] +1: pop $r11 + pop $r10 + pop $dccr + retb + nop + +IRQ1_interrupt: + ;; this prologue MUST match the one in irq.h and the struct in ptregs.h!!! + move $brp,[$sp=$sp-16]; instruction pointer and room for a fake SBFS frame + push $srp + push $dccr + push $mof + di + subq 14*4, $sp + movem $r13, [$sp] + push $r10 ; push orig_r10 + clear.d [$sp=$sp-4] ; frametype == 0, normal frame + + ;; If there is a glitch on the NMI pin shorter than ~100ns + ;; (i.e. non-active by the time we get here) then the nmi_pin bit + ;; in R_IRQ_MASK0_RD will already be cleared. The watchdog_nmi bit + ;; is cleared by us however (when feeding the watchdog), which is why + ;; we use that bit to determine what brought us here. + + move.d [R_IRQ_MASK0_RD], $r1 ; External NMI or watchdog? + and.d (1<<30), $r1 + bne wdog + move.d $sp, $r10 + jsr handle_nmi + setf m ; Enable NMI again + ba _Rexit ; Return the standard way + nop +wdog: +#if defined(CONFIG_ETRAX_WATCHDOG) +;; Check if we're waiting for reset to happen, as signalled by +;; hard_reset_now setting cause_of_death to a magic value. If so, just +;; get stuck until reset happens. + .comm cause_of_death, 4 ;; Don't declare this anywhere. + move.d [cause_of_death], $r10 + cmp.d 0xbedead, $r10 +_killed_by_death: + beq _killed_by_death + nop + +;; We'll see this in ksymoops dumps. +Watchdog_bite: + +#ifdef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY + ;; We just restart the watchdog here to be sure we dont get + ;; hit while printing the watchdogmsg below + ;; This restart is compatible with the rest of the C-code, so + ;; the C-code can keep restarting the watchdog after this point. + ;; The non-NICE_DOGGY code below though, disables the possibility + ;; to restart since it changes the watchdog key, to avoid any + ;; buggy loops etc. keeping the watchdog alive after this. + jsr reset_watchdog +#else + +;; We need to extend the 3.3ms after the NMI at watchdog bite, so we have +;; time for an oops-dump over a 115k2 serial wire. Another 100ms should do. + +;; Change the watchdog key to an arbitrary 3-bit value and restart the +;; watchdog. +#define WD_INIT 2 + moveq IO_FIELD (R_WATCHDOG, key, WD_INIT), $r10 + move.d R_WATCHDOG, $r11 + + move.d $r10, [$r11] + moveq IO_FIELD (R_WATCHDOG, key, \ + IO_EXTRACT (R_WATCHDOG, key, \ + IO_MASK (R_WATCHDOG, key)) \ + ^ WD_INIT) \ + | IO_STATE (R_WATCHDOG, enable, start), $r10 + move.d $r10, [$r11] + +#endif + +;; Note that we don't do "setf m" here (or after two necessary NOPs), +;; since *not* doing that saves us from re-entrancy checks. We don't want +;; to get here again due to possible subsequent NMIs; we want the watchdog +;; to reset us. + + move.d _watchdogmsg,$r10 + jsr printk + + move.d $sp, $r10 + jsr watchdog_bite_hook + +;; This nop is here so we see the "Watchdog_bite" label in ksymoops dumps +;; rather than "spurious_interrupt". + nop +;; At this point we drop down into spurious_interrupt, which will do a +;; hard reset. + + .section .rodata,"a" +_watchdogmsg: + .ascii "Oops: bitten by watchdog\n\0" + .previous + +#endif /* CONFIG_ETRAX_WATCHDOG */ + +spurious_interrupt: + di + jump hard_reset_now + + ;; this handles the case when multiple interrupts arrive at the same time + ;; we jump to the first set interrupt bit in a priority fashion + ;; the hardware will call the unserved interrupts after the handler finishes + +multiple_interrupt: + ;; this prologue MUST match the one in irq.h and the struct in ptregs.h!!! + move $irp,[$sp=$sp-16]; instruction pointer and room for a fake SBFS frame + push $srp + push $dccr + push $mof + di + subq 14*4, $sp + movem $r13, [$sp] + push $r10 ; push orig_r10 + clear.d [$sp=$sp-4] ; frametype == 0, normal frame + + move.d $sp, $r10 + jsr do_multiple_IRQ + + jump ret_from_intr + +do_sigtrap: + ;; + ;; SIGTRAP the process that executed the break instruction. + ;; Make a frame that Rexit in entry.S expects. + ;; + move $brp, [$sp=$sp-16] ; Push BRP while faking a cpu status record. + push $srp ; Push subroutine return pointer. + push $dccr ; Push condition codes. + push $mof ; Push multiply overflow reg. + di ; Need to disable irq's at this point. + subq 14*4, $sp ; Make room for r0-r13. + movem $r13, [$sp] ; Push the r0-r13 registers. + push $r10 ; Push orig_r10. + clear.d [$sp=$sp-4] ; Frametype - this is a normal stackframe. + + movs.w -8192,$r9 ; THREAD_SIZE == 8192 + and.d $sp, $r9 + move.d [$r9+TI_task], $r10 + move.d [$r10+TASK_pid], $r10 ; current->pid as arg1. + moveq 5, $r11 ; SIGTRAP as arg2. + jsr sys_kill + jump ret_from_intr ; Use the return routine for interrupts. + +gdb_handle_breakpoint: + push $dccr + push $r0 +#ifdef CONFIG_ETRAX_KGDB + move $dccr, $r0 ; U-flag not affected by previous insns. + btstq 8, $r0 ; Test the U-flag. + bmi _ugdb_handle_breakpoint ; Go to user mode debugging. + nop ; Empty delay slot (cannot pop r0 here). + pop $r0 ; Restore r0. + ba kgdb_handle_breakpoint ; Go to kernel debugging. + pop $dccr ; Restore dccr in delay slot. +#endif + +_ugdb_handle_breakpoint: + move $brp, $r0 ; Use r0 temporarily for calculation. + subq 2, $r0 ; Set to address of previous instruction. + move $r0, $brp + pop $r0 ; Restore r0. + ba do_sigtrap ; SIGTRAP the offending process. + pop $dccr ; Restore dccr in delay slot. + + .data + +hw_bp_trigs: + .space 64*4 +hw_bp_trig_ptr: + .dword hw_bp_trigs + + .section .rodata,"a" +sys_call_table: + .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */ + .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 /* old sys_olduname holder */ + .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_select /* was old_select in Linux/E100 */ + .long sys_symlink + .long sys_lstat + .long sys_readlink /* 85 */ + .long sys_uselib + .long sys_swapon + .long sys_reboot + .long sys_old_readdir + .long sys_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 /* sys_ioperm in i386 */ + .long sys_socketcall + .long sys_syslog + .long sys_setitimer + .long sys_getitimer /* 105 */ + .long sys_newstat + .long sys_newlstat + .long sys_newfstat + .long sys_ni_syscall /* old sys_uname holder */ + .long sys_ni_syscall /* 110 */ /* sys_iopl in i386 */ + .long sys_vhangup + .long sys_ni_syscall /* old "idle" system call */ + .long sys_ni_syscall /* vm86old in i386 */ + .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 /* sys_vm86 */ + .long sys_ni_syscall /* Old sys_query_module */ + .long sys_poll + .long sys_ni_syscall /* old nfsservctl */ + .long sys_setresgid16 /* 170 */ + .long sys_getresgid16 + .long sys_prctl + .long sys_rt_sigreturn + .long sys_rt_sigaction + .long sys_rt_sigprocmask /* 175 */ + .long sys_rt_sigpending + .long sys_rt_sigtimedwait + .long sys_rt_sigqueueinfo + .long sys_rt_sigsuspend + .long sys_pread64 /* 180 */ + .long sys_pwrite64 + .long sys_chown16 + .long sys_getcwd + .long sys_capget + .long sys_capset /* 185 */ + .long sys_sigaltstack + .long sys_sendfile + .long sys_ni_syscall /* streams1 */ + .long sys_ni_syscall /* streams2 */ + .long sys_vfork /* 190 */ + .long sys_getrlimit + .long sys_mmap2 /* mmap_pgoff */ + .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 + .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 /* sys_set_thread_area */ + .long sys_ni_syscall /* sys_get_thread_area */ + .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 + .long sys_ni_syscall /* sys_vserver */ + .long sys_ni_syscall /* sys_mbind */ + .long sys_ni_syscall /* 275 sys_get_mempolicy */ + .long sys_ni_syscall /* sys_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 + .long sys_waitid + .long sys_ni_syscall /* 285 */ /* available */ + .long sys_add_key + .long sys_request_key + .long sys_keyctl + .long sys_ioprio_set + .long sys_ioprio_get /* 290 */ + .long sys_inotify_init + .long sys_inotify_add_watch + .long sys_inotify_rm_watch + .long sys_migrate_pages + .long sys_openat /* 295 */ + .long sys_mkdirat + .long sys_mknodat + .long sys_fchownat + .long sys_futimesat + .long sys_fstatat64 /* 300 */ + .long sys_unlinkat + .long sys_renameat + .long sys_linkat + .long sys_symlinkat + .long sys_readlinkat /* 305 */ + .long sys_fchmodat + .long sys_faccessat + .long sys_pselect6 + .long sys_ppoll + .long sys_unshare /* 310 */ + .long sys_set_robust_list + .long sys_get_robust_list + .long sys_splice + .long sys_sync_file_range + .long sys_tee /* 315 */ + .long sys_vmsplice + .long sys_move_pages + .long sys_getcpu + .long sys_epoll_pwait + .long sys_utimensat /* 320 */ + .long sys_signalfd + .long sys_timerfd_create + .long sys_eventfd + .long sys_fallocate + .long sys_timerfd_settime /* 325 */ + .long sys_timerfd_gettime + .long sys_signalfd4 + .long sys_eventfd2 + .long sys_epoll_create1 + .long sys_dup3 /* 330 */ + .long sys_pipe2 + .long sys_inotify_init1 + .long sys_preadv + .long sys_pwritev + .long sys_setns /* 335 */ + .long sys_name_to_handle_at + .long sys_open_by_handle_at + .long sys_rt_tgsigqueueinfo + .long sys_perf_event_open + .long sys_recvmmsg /* 340 */ + .long sys_accept4 + .long sys_fanotify_init + .long sys_fanotify_mark + .long sys_prlimit64 + .long sys_clock_adjtime /* 345 */ + .long sys_syncfs + .long sys_sendmmsg + .long sys_process_vm_readv + .long sys_process_vm_writev + .long sys_kcmp /* 350 */ + .long sys_finit_module + + /* + * NOTE!! This doesn't have to be exact - we just have + * to make sure we have _enough_ of the "sys_ni_syscall" + * entries. Don't panic if you notice that this hasn't + * been shrunk every time we add a new system call. + */ + + .rept NR_syscalls-(.-sys_call_table)/4 + .long sys_ni_syscall + .endr + diff --git a/arch/cris/arch-v10/kernel/fasttimer.c b/arch/cris/arch-v10/kernel/fasttimer.c new file mode 100644 index 000000000..e9298739d --- /dev/null +++ b/arch/cris/arch-v10/kernel/fasttimer.c @@ -0,0 +1,834 @@ +/* + * linux/arch/cris/kernel/fasttimer.c + * + * Fast timers for ETRAX100/ETRAX100LX + * + * Copyright (C) 2000-2007 Axis Communications AB, Lund, Sweden + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/delay.h> + +#include <asm/segment.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/delay.h> + +#include <arch/svinto.h> +#include <asm/fasttimer.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> + + +#define DEBUG_LOG_INCLUDED +#define FAST_TIMER_LOG +/* #define FAST_TIMER_TEST */ + +#define FAST_TIMER_SANITY_CHECKS + +#ifdef FAST_TIMER_SANITY_CHECKS +static int sanity_failed; +#endif + +#define D1(x) +#define D2(x) +#define DP(x) + +static unsigned int fast_timer_running; +static unsigned int fast_timers_added; +static unsigned int fast_timers_started; +static unsigned int fast_timers_expired; +static unsigned int fast_timers_deleted; +static unsigned int fast_timer_is_init; +static unsigned int fast_timer_ints; + +struct fast_timer *fast_timer_list = NULL; + +#ifdef DEBUG_LOG_INCLUDED +#define DEBUG_LOG_MAX 128 +static const char * debug_log_string[DEBUG_LOG_MAX]; +static unsigned long debug_log_value[DEBUG_LOG_MAX]; +static unsigned int debug_log_cnt; +static unsigned int debug_log_cnt_wrapped; + +#define DEBUG_LOG(string, value) \ +{ \ + unsigned long log_flags; \ + local_irq_save(log_flags); \ + debug_log_string[debug_log_cnt] = (string); \ + debug_log_value[debug_log_cnt] = (unsigned long)(value); \ + if (++debug_log_cnt >= DEBUG_LOG_MAX) \ + { \ + debug_log_cnt = debug_log_cnt % DEBUG_LOG_MAX; \ + debug_log_cnt_wrapped = 1; \ + } \ + local_irq_restore(log_flags); \ +} +#else +#define DEBUG_LOG(string, value) +#endif + + +/* The frequencies for index = clkselx number in R_TIMER_CTRL */ +#define NUM_TIMER_FREQ 15 +#define MAX_USABLE_TIMER_FREQ 7 +#define MAX_DELAY_US 853333L +const unsigned long timer_freq_100[NUM_TIMER_FREQ] = +{ + 3, /* 0 3333 - 853333 us */ + 6, /* 1 1666 - 426666 us */ + 12, /* 2 833 - 213333 us */ + 24, /* 3 416 - 106666 us */ + 48, /* 4 208 - 53333 us */ + 96, /* 5 104 - 26666 us */ + 192, /* 6 52 - 13333 us */ + 384, /* 7 26 - 6666 us */ + 576, + 1152, + 2304, + 4608, + 9216, + 18432, + 62500, + /* 15 = cascade */ +}; +#define NUM_TIMER_STATS 16 +#ifdef FAST_TIMER_LOG +struct fast_timer timer_added_log[NUM_TIMER_STATS]; +struct fast_timer timer_started_log[NUM_TIMER_STATS]; +struct fast_timer timer_expired_log[NUM_TIMER_STATS]; +#endif + +int timer_div_settings[NUM_TIMER_STATS]; +int timer_freq_settings[NUM_TIMER_STATS]; +int timer_delay_settings[NUM_TIMER_STATS]; + +/* Not true gettimeofday, only checks the jiffies (uptime) + useconds */ +inline void do_gettimeofday_fast(struct fasttime_t *tv) +{ + tv->tv_jiff = jiffies; + tv->tv_usec = GET_JIFFIES_USEC(); +} + +inline int fasttime_cmp(struct fasttime_t *t0, struct fasttime_t *t1) +{ + /* Compare jiffies. Takes care of wrapping */ + if (time_before(t0->tv_jiff, t1->tv_jiff)) + return -1; + else if (time_after(t0->tv_jiff, t1->tv_jiff)) + return 1; + + /* Compare us */ + if (t0->tv_usec < t1->tv_usec) + return -1; + else if (t0->tv_usec > t1->tv_usec) + return 1; + return 0; +} + +inline void start_timer1(unsigned long delay_us) +{ + int freq_index = 0; /* This is the lowest resolution */ + unsigned long upper_limit = MAX_DELAY_US; + + unsigned long div; + /* Start/Restart the timer to the new shorter value */ + /* t = 1/freq = 1/19200 = 53us + * T=div*t, div = T/t = delay_us*freq/1000000 + */ +#if 1 /* Adaptive timer settings */ + while (delay_us < upper_limit && freq_index < MAX_USABLE_TIMER_FREQ) + { + freq_index++; + upper_limit >>= 1; /* Divide by 2 using shift */ + } + if (freq_index > 0) + { + freq_index--; + } +#else + freq_index = 6; +#endif + div = delay_us * timer_freq_100[freq_index]/10000; + if (div < 2) + { + /* Maybe increase timer freq? */ + div = 2; + } + if (div > 255) + { + div = 0; /* This means 256, the max the timer takes */ + /* If a longer timeout than the timer can handle is used, + * then we must restart it when it goes off. + */ + } + + timer_div_settings[fast_timers_started % NUM_TIMER_STATS] = div; + timer_freq_settings[fast_timers_started % NUM_TIMER_STATS] = freq_index; + timer_delay_settings[fast_timers_started % NUM_TIMER_STATS] = delay_us; + + D1(printk(KERN_DEBUG "start_timer1 : %d us freq: %i div: %i\n", + delay_us, freq_index, div)); + /* Clear timer1 irq */ + *R_IRQ_MASK0_CLR = IO_STATE(R_IRQ_MASK0_CLR, timer1, clr); + + /* Set timer values */ + *R_TIMER_CTRL = r_timer_ctrl_shadow = + (r_timer_ctrl_shadow & + ~IO_MASK(R_TIMER_CTRL, timerdiv1) & + ~IO_MASK(R_TIMER_CTRL, tm1) & + ~IO_MASK(R_TIMER_CTRL, clksel1)) | + IO_FIELD(R_TIMER_CTRL, timerdiv1, div) | + IO_STATE(R_TIMER_CTRL, tm1, stop_ld) | + IO_FIELD(R_TIMER_CTRL, clksel1, freq_index ); /* 6=c19k2Hz */ + + /* Ack interrupt */ + *R_TIMER_CTRL = r_timer_ctrl_shadow | + IO_STATE(R_TIMER_CTRL, i1, clr); + + /* Start timer */ + *R_TIMER_CTRL = r_timer_ctrl_shadow = + (r_timer_ctrl_shadow & ~IO_MASK(R_TIMER_CTRL, tm1)) | + IO_STATE(R_TIMER_CTRL, tm1, run); + + /* Enable timer1 irq */ + *R_IRQ_MASK0_SET = IO_STATE(R_IRQ_MASK0_SET, timer1, set); + fast_timers_started++; + fast_timer_running = 1; +} + +/* In version 1.4 this function takes 27 - 50 us */ +void start_one_shot_timer(struct fast_timer *t, + fast_timer_function_type *function, + unsigned long data, + unsigned long delay_us, + const char *name) +{ + unsigned long flags; + struct fast_timer *tmp; + + D1(printk("sft %s %d us\n", name, delay_us)); + + local_irq_save(flags); + + do_gettimeofday_fast(&t->tv_set); + tmp = fast_timer_list; + +#ifdef FAST_TIMER_SANITY_CHECKS + /* Check so this is not in the list already... */ + while (tmp != NULL) { + if (tmp == t) { + printk(KERN_WARNING "timer name: %s data: " + "0x%08lX already in list!\n", name, data); + sanity_failed++; + goto done; + } else + tmp = tmp->next; + } + tmp = fast_timer_list; +#endif + + t->delay_us = delay_us; + t->function = function; + t->data = data; + t->name = name; + + t->tv_expires.tv_usec = t->tv_set.tv_usec + delay_us % 1000000; + t->tv_expires.tv_jiff = t->tv_set.tv_jiff + delay_us / 1000000 / HZ; + if (t->tv_expires.tv_usec > 1000000) + { + t->tv_expires.tv_usec -= 1000000; + t->tv_expires.tv_jiff += HZ; + } +#ifdef FAST_TIMER_LOG + timer_added_log[fast_timers_added % NUM_TIMER_STATS] = *t; +#endif + fast_timers_added++; + + /* Check if this should timeout before anything else */ + if (tmp == NULL || fasttime_cmp(&t->tv_expires, &tmp->tv_expires) < 0) + { + /* Put first in list and modify the timer value */ + t->prev = NULL; + t->next = fast_timer_list; + if (fast_timer_list) + { + fast_timer_list->prev = t; + } + fast_timer_list = t; +#ifdef FAST_TIMER_LOG + timer_started_log[fast_timers_started % NUM_TIMER_STATS] = *t; +#endif + start_timer1(delay_us); + } else { + /* Put in correct place in list */ + while (tmp->next && fasttime_cmp(&t->tv_expires, + &tmp->next->tv_expires) > 0) + { + tmp = tmp->next; + } + /* Insert t after tmp */ + t->prev = tmp; + t->next = tmp->next; + if (tmp->next) + { + tmp->next->prev = t; + } + tmp->next = t; + } + + D2(printk("start_one_shot_timer: %d us done\n", delay_us)); + +done: + local_irq_restore(flags); +} /* start_one_shot_timer */ + +static inline int fast_timer_pending (const struct fast_timer * t) +{ + return (t->next != NULL) || (t->prev != NULL) || (t == fast_timer_list); +} + +static inline int detach_fast_timer (struct fast_timer *t) +{ + struct fast_timer *next, *prev; + if (!fast_timer_pending(t)) + return 0; + next = t->next; + prev = t->prev; + if (next) + next->prev = prev; + if (prev) + prev->next = next; + else + fast_timer_list = next; + fast_timers_deleted++; + return 1; +} + +int del_fast_timer(struct fast_timer * t) +{ + unsigned long flags; + int ret; + + local_irq_save(flags); + ret = detach_fast_timer(t); + t->next = t->prev = NULL; + local_irq_restore(flags); + return ret; +} /* del_fast_timer */ + + +/* Interrupt routines or functions called in interrupt context */ + +/* Timer 1 interrupt handler */ + +static irqreturn_t +timer1_handler(int irq, void *dev_id) +{ + struct fast_timer *t; + unsigned long flags; + + /* We keep interrupts disabled not only when we modify the + * fast timer list, but any time we hold a reference to a + * timer in the list, since del_fast_timer may be called + * from (another) interrupt context. Thus, the only time + * when interrupts are enabled is when calling the timer + * callback function. + */ + local_irq_save(flags); + + /* Clear timer1 irq */ + *R_IRQ_MASK0_CLR = IO_STATE(R_IRQ_MASK0_CLR, timer1, clr); + + /* First stop timer, then ack interrupt */ + /* Stop timer */ + *R_TIMER_CTRL = r_timer_ctrl_shadow = + (r_timer_ctrl_shadow & ~IO_MASK(R_TIMER_CTRL, tm1)) | + IO_STATE(R_TIMER_CTRL, tm1, stop_ld); + + /* Ack interrupt */ + *R_TIMER_CTRL = r_timer_ctrl_shadow | IO_STATE(R_TIMER_CTRL, i1, clr); + + fast_timer_running = 0; + fast_timer_ints++; + + t = fast_timer_list; + while (t) + { + struct fasttime_t tv; + fast_timer_function_type *f; + unsigned long d; + + /* Has it really expired? */ + do_gettimeofday_fast(&tv); + D1(printk(KERN_DEBUG "t: %is %06ius\n", + tv.tv_jiff, tv.tv_usec)); + + if (fasttime_cmp(&t->tv_expires, &tv) <= 0) + { + /* Yes it has expired */ +#ifdef FAST_TIMER_LOG + timer_expired_log[fast_timers_expired % NUM_TIMER_STATS] = *t; +#endif + fast_timers_expired++; + + /* Remove this timer before call, since it may reuse the timer */ + if (t->prev) + { + t->prev->next = t->next; + } + else + { + fast_timer_list = t->next; + } + if (t->next) + { + t->next->prev = t->prev; + } + t->prev = NULL; + t->next = NULL; + + /* Save function callback data before enabling + * interrupts, since the timer may be removed and + * we don't know how it was allocated + * (e.g. ->function and ->data may become overwritten + * after deletion if the timer was stack-allocated). + */ + f = t->function; + d = t->data; + + if (f != NULL) { + /* Run callback with interrupts enabled. */ + local_irq_restore(flags); + f(d); + local_irq_save(flags); + } else + DEBUG_LOG("!timer1 %i function==NULL!\n", fast_timer_ints); + } + else + { + /* Timer is to early, let's set it again using the normal routines */ + D1(printk(".\n")); + } + + if ((t = fast_timer_list) != NULL) + { + /* Start next timer.. */ + long us = 0; + struct fasttime_t tv; + + do_gettimeofday_fast(&tv); + + /* time_after_eq takes care of wrapping */ + if (time_after_eq(t->tv_expires.tv_jiff, tv.tv_jiff)) + us = ((t->tv_expires.tv_jiff - tv.tv_jiff) * + 1000000 / HZ + t->tv_expires.tv_usec - + tv.tv_usec); + + if (us > 0) + { + if (!fast_timer_running) + { +#ifdef FAST_TIMER_LOG + timer_started_log[fast_timers_started % NUM_TIMER_STATS] = *t; +#endif + start_timer1(us); + } + break; + } + else + { + /* Timer already expired, let's handle it better late than never. + * The normal loop handles it + */ + D1(printk("e! %d\n", us)); + } + } + } + + local_irq_restore(flags); + + if (!t) + { + D1(printk("t1 stop!\n")); + } + + return IRQ_HANDLED; +} + +static void wake_up_func(unsigned long data) +{ + wait_queue_head_t *sleep_wait_p = (wait_queue_head_t *)data; + wake_up(sleep_wait_p); +} + + +/* Useful API */ + +void schedule_usleep(unsigned long us) +{ + struct fast_timer t; + wait_queue_head_t sleep_wait; + init_waitqueue_head(&sleep_wait); + + D1(printk("schedule_usleep(%d)\n", us)); + start_one_shot_timer(&t, wake_up_func, (unsigned long)&sleep_wait, us, + "usleep"); + /* Uninterruptible sleep on the fast timer. (The condition is somewhat + * redundant since the timer is what wakes us up.) */ + wait_event(sleep_wait, !fast_timer_pending(&t)); + + D1(printk("done schedule_usleep(%d)\n", us)); +} + +#ifdef CONFIG_PROC_FS +/* This value is very much based on testing */ +#define BIG_BUF_SIZE (500 + NUM_TIMER_STATS * 300) + +static int proc_fasttimer_show(struct seq_file *m, void *v) +{ + unsigned long flags; + int i = 0; + int num_to_show; + struct fasttime_t tv; + struct fast_timer *t, *nextt; + + do_gettimeofday_fast(&tv); + + seq_printf(m, "Fast timers added: %i\n", fast_timers_added); + seq_printf(m, "Fast timers started: %i\n", fast_timers_started); + seq_printf(m, "Fast timer interrupts: %i\n", fast_timer_ints); + seq_printf(m, "Fast timers expired: %i\n", fast_timers_expired); + seq_printf(m, "Fast timers deleted: %i\n", fast_timers_deleted); + seq_printf(m, "Fast timer running: %s\n", + fast_timer_running ? "yes" : "no"); + seq_printf(m, "Current time: %lu.%06lu\n", + (unsigned long)tv.tv_jiff, + (unsigned long)tv.tv_usec); +#ifdef FAST_TIMER_SANITY_CHECKS + seq_printf(m, "Sanity failed: %i\n", sanity_failed); +#endif + seq_putc(m, '\n'); + +#ifdef DEBUG_LOG_INCLUDED + { + int end_i = debug_log_cnt; + i = 0; + + if (debug_log_cnt_wrapped) + i = debug_log_cnt; + + while (i != end_i || debug_log_cnt_wrapped) { + seq_printf(m, debug_log_string[i], debug_log_value[i]); + if (seq_has_overflowed(m)) + return 0; + i = (i+1) % DEBUG_LOG_MAX; + } + } + seq_putc(m, '\n'); +#endif + + num_to_show = (fast_timers_started < NUM_TIMER_STATS ? fast_timers_started: + NUM_TIMER_STATS); + seq_printf(m, "Timers started: %i\n", fast_timers_started); + for (i = 0; i < num_to_show; i++) { + int cur = (fast_timers_started - i - 1) % NUM_TIMER_STATS; + +#if 1 //ndef FAST_TIMER_LOG + seq_printf(m, "div: %i freq: %i delay: %i\n", + timer_div_settings[cur], + timer_freq_settings[cur], + timer_delay_settings[cur]); +#endif +#ifdef FAST_TIMER_LOG + t = &timer_started_log[cur]; + seq_printf(m, "%-14s s: %6lu.%06lu e: %6lu.%06lu d: %6li us data: 0x%08lX\n", + t->name, + (unsigned long)t->tv_set.tv_jiff, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_jiff, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data); + if (seq_has_overflowed(m)) + return 0; +#endif + } + seq_putc(m, '\n'); + +#ifdef FAST_TIMER_LOG + num_to_show = (fast_timers_added < NUM_TIMER_STATS ? fast_timers_added: + NUM_TIMER_STATS); + seq_printf(m, "Timers added: %i\n", fast_timers_added); + for (i = 0; i < num_to_show; i++) { + t = &timer_added_log[(fast_timers_added - i - 1) % NUM_TIMER_STATS]; + seq_printf(m, "%-14s s: %6lu.%06lu e: %6lu.%06lu d: %6li us data: 0x%08lX\n", + t->name, + (unsigned long)t->tv_set.tv_jiff, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_jiff, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data); + if (seq_has_overflowed(m)) + return 0; + } + seq_putc(m, '\n'); + + num_to_show = (fast_timers_expired < NUM_TIMER_STATS ? fast_timers_expired: + NUM_TIMER_STATS); + seq_printf(m, "Timers expired: %i\n", fast_timers_expired); + for (i = 0; i < num_to_show; i++) { + t = &timer_expired_log[(fast_timers_expired - i - 1) % NUM_TIMER_STATS]; + seq_printf(m, "%-14s s: %6lu.%06lu e: %6lu.%06lu d: %6li us data: 0x%08lX\n", + t->name, + (unsigned long)t->tv_set.tv_jiff, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_jiff, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data); + if (seq_has_overflowed(m)) + return 0; + } + seq_putc(m, '\n'); +#endif + + seq_puts(m, "Active timers:\n"); + local_irq_save(flags); + t = fast_timer_list; + while (t) { + nextt = t->next; + local_irq_restore(flags); + seq_printf(m, "%-14s s: %6lu.%06lu e: %6lu.%06lu d: %6li us data: 0x%08lX\n", + t->name, + (unsigned long)t->tv_set.tv_jiff, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_jiff, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data); + if (seq_has_overflowed(m)) + return 0; + local_irq_save(flags); + if (t->next != nextt) + printk(KERN_WARNING "timer removed!\n"); + t = nextt; + } + local_irq_restore(flags); + + return 0; +} + +static int proc_fasttimer_open(struct inode *inode, struct file *file) +{ + return single_open_size(file, proc_fasttimer_show, PDE_DATA(inode), BIG_BUF_SIZE); +} + +static const struct file_operations proc_fasttimer_fops = { + .open = proc_fasttimer_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif /* PROC_FS */ + +#ifdef FAST_TIMER_TEST +static volatile unsigned long i = 0; +static volatile int num_test_timeout = 0; +static struct fast_timer tr[10]; +static int exp_num[10]; + +static struct fasttime_t tv_exp[100]; + +static void test_timeout(unsigned long data) +{ + do_gettimeofday_fast(&tv_exp[data]); + exp_num[data] = num_test_timeout; + + num_test_timeout++; +} + +static void test_timeout1(unsigned long data) +{ + do_gettimeofday_fast(&tv_exp[data]); + exp_num[data] = num_test_timeout; + if (data < 7) + { + start_one_shot_timer(&tr[i], test_timeout1, i, 1000, "timeout1"); + i++; + } + num_test_timeout++; +} + +DP( +static char buf0[2000]; +static char buf1[2000]; +static char buf2[2000]; +static char buf3[2000]; +static char buf4[2000]; +); + +static char buf5[6000]; +static int j_u[1000]; + +static void fast_timer_test(void) +{ + int prev_num; + int j; + + struct fasttime_t tv, tv0, tv1, tv2; + + printk("fast_timer_test() start\n"); + do_gettimeofday_fast(&tv); + + for (j = 0; j < 1000; j++) + { + j_u[j] = GET_JIFFIES_USEC(); + } + for (j = 0; j < 100; j++) + { + do_gettimeofday_fast(&tv_exp[j]); + } + printk(KERN_DEBUG "fast_timer_test() %is %06i\n", + tv.tv_jiff, tv.tv_usec); + + for (j = 0; j < 1000; j++) + { + printk("%i %i %i %i %i\n",j_u[j], j_u[j+1], j_u[j+2], j_u[j+3], j_u[j+4]); + j += 4; + } + for (j = 0; j < 100; j++) + { + printk(KERN_DEBUG "%i.%i %i.%i %i.%i %i.%i %i.%i\n", + tv_exp[j].tv_jiff, tv_exp[j].tv_usec, + tv_exp[j+1].tv_jiff, tv_exp[j+1].tv_usec, + tv_exp[j+2].tv_jiff, tv_exp[j+2].tv_usec, + tv_exp[j+3].tv_jiff, tv_exp[j+3].tv_usec, + tv_exp[j+4].tv_jiff, tv_exp[j+4].tv_usec); + j += 4; + } + do_gettimeofday_fast(&tv0); + start_one_shot_timer(&tr[i], test_timeout, i, 50000, "test0"); + DP(proc_fasttimer_read(buf0, NULL, 0, 0, 0)); + i++; + start_one_shot_timer(&tr[i], test_timeout, i, 70000, "test1"); + DP(proc_fasttimer_read(buf1, NULL, 0, 0, 0)); + i++; + start_one_shot_timer(&tr[i], test_timeout, i, 40000, "test2"); + DP(proc_fasttimer_read(buf2, NULL, 0, 0, 0)); + i++; + start_one_shot_timer(&tr[i], test_timeout, i, 60000, "test3"); + DP(proc_fasttimer_read(buf3, NULL, 0, 0, 0)); + i++; + start_one_shot_timer(&tr[i], test_timeout1, i, 55000, "test4xx"); + DP(proc_fasttimer_read(buf4, NULL, 0, 0, 0)); + i++; + do_gettimeofday_fast(&tv1); + + proc_fasttimer_read(buf5, NULL, 0, 0, 0); + + prev_num = num_test_timeout; + while (num_test_timeout < i) + { + if (num_test_timeout != prev_num) + { + prev_num = num_test_timeout; + } + } + do_gettimeofday_fast(&tv2); + printk(KERN_DEBUG "Timers started %is %06i\n", + tv0.tv_jiff, tv0.tv_usec); + printk(KERN_DEBUG "Timers started at %is %06i\n", + tv1.tv_jiff, tv1.tv_usec); + printk(KERN_DEBUG "Timers done %is %06i\n", + tv2.tv_jiff, tv2.tv_usec); + DP(printk("buf0:\n"); + printk(buf0); + printk("buf1:\n"); + printk(buf1); + printk("buf2:\n"); + printk(buf2); + printk("buf3:\n"); + printk(buf3); + printk("buf4:\n"); + printk(buf4); + ); + printk("buf5:\n"); + printk(buf5); + + printk("timers set:\n"); + for(j = 0; j<i; j++) + { + struct fast_timer *t = &tr[j]; + printk("%-10s set: %6is %06ius exp: %6is %06ius " + "data: 0x%08X func: 0x%08X\n", + t->name, + t->tv_set.tv_jiff, + t->tv_set.tv_usec, + t->tv_expires.tv_jiff, + t->tv_expires.tv_usec, + t->data, + t->function + ); + + printk(" del: %6ius did exp: %6is %06ius as #%i error: %6li\n", + t->delay_us, + tv_exp[j].tv_jiff, + tv_exp[j].tv_usec, + exp_num[j], + (tv_exp[j].tv_jiff - t->tv_expires.tv_jiff) * + 1000000 + tv_exp[j].tv_usec - + t->tv_expires.tv_usec); + } + proc_fasttimer_read(buf5, NULL, 0, 0, 0); + printk("buf5 after all done:\n"); + printk(buf5); + printk("fast_timer_test() done\n"); +} +#endif + + +int fast_timer_init(void) +{ + /* For some reason, request_irq() hangs when called froom time_init() */ + if (!fast_timer_is_init) + { +#if 0 && defined(FAST_TIMER_TEST) + int i; +#endif + + printk(KERN_INFO "fast_timer_init()\n"); + +#if 0 && defined(FAST_TIMER_TEST) + for (i = 0; i <= TIMER0_DIV; i++) + { + /* We must be careful not to get overflow... */ + printk("%3i %6u\n", i, timer0_value_us[i]); + } +#endif +#ifdef CONFIG_PROC_FS + proc_create("fasttimer", 0, NULL, &proc_fasttimer_fops); +#endif /* PROC_FS */ + if(request_irq(TIMER1_IRQ_NBR, timer1_handler, 0, + "fast timer int", NULL)) + { + printk("err: timer1 irq\n"); + } + fast_timer_is_init = 1; +#ifdef FAST_TIMER_TEST + printk("do test\n"); + fast_timer_test(); +#endif + } + return 0; +} +__initcall(fast_timer_init); diff --git a/arch/cris/arch-v10/kernel/head.S b/arch/cris/arch-v10/kernel/head.S new file mode 100644 index 000000000..4a146e174 --- /dev/null +++ b/arch/cris/arch-v10/kernel/head.S @@ -0,0 +1,723 @@ +/* + * Head of the kernel - alter with care + * + * Copyright (C) 2000, 2001, 2010 Axis Communications AB + * + */ + +#define ASSEMBLER_MACROS_ONLY +/* The IO_* macros use the ## token concatenation operator, so + -traditional must not be used when assembling this file. */ +#include <arch/sv_addr_ag.h> + +#define CRAMFS_MAGIC 0x28cd3d45 +#define RAM_INIT_MAGIC 0x56902387 +#define COMMAND_LINE_MAGIC 0x87109563 + +#define START_ETHERNET_CLOCK IO_STATE(R_NETWORK_GEN_CONFIG, enable, on) |\ + IO_STATE(R_NETWORK_GEN_CONFIG, phy, mii_clk) + + ;; exported symbols + + .globl etrax_irv + .globl romfs_start + .globl romfs_length + .globl romfs_in_flash + .globl swapper_pg_dir + + .text + + ;; This is the entry point of the kernel. We are in supervisor mode. + ;; 0x00000000 if Flash, 0x40004000 if DRAM + ;; since etrax actually starts at address 2 when booting from flash, we + ;; put a nop (2 bytes) here first so we dont accidentally skip the di + ;; + ;; NOTICE! The registers r8 and r9 are used as parameters carrying + ;; information from the decompressor (if the kernel was compressed). + ;; They should not be used in the code below until read. + + nop + di + + ;; First setup the kseg_c mapping from where the kernel is linked + ;; to 0x40000000 (where the actual DRAM resides) otherwise + ;; we cannot do very much! See arch/cris/README.mm + ;; + ;; Notice that since we're potentially running at 0x00 or 0x40 right now, + ;; we will get a fault as soon as we enable the MMU if we dont + ;; temporarily map those segments linearily. + ;; + ;; Due to a bug in Etrax-100 LX version 1 we need to map the memory + ;; slightly different. The bug is that you can't remap bit 31 of + ;; an address. Though we can check the version register for + ;; whether the bug is present, some constants would then have to + ;; be variables, so we don't. The drawback is that you can "only" map + ;; 1G per process with CONFIG_CRIS_LOW_MAP. + +#ifdef CONFIG_CRIS_LOW_MAP + ; kseg mappings, temporary map of 0xc0->0x40 + move.d IO_FIELD (R_MMU_KBASE_HI, base_c, 4) \ + | IO_FIELD (R_MMU_KBASE_HI, base_b, 0xb) \ + | IO_FIELD (R_MMU_KBASE_HI, base_9, 9) \ + | IO_FIELD (R_MMU_KBASE_HI, base_8, 8), $r0 + move.d $r0, [R_MMU_KBASE_HI] + + ; temporary map of 0x40->0x40 and 0x60->0x40 + move.d IO_FIELD (R_MMU_KBASE_LO, base_6, 4) \ + | IO_FIELD (R_MMU_KBASE_LO, base_4, 4), $r0 + move.d $r0, [R_MMU_KBASE_LO] + + ; mmu enable, segs e,c,b,a,6,5,4,0 segment mapped + move.d IO_STATE (R_MMU_CONFIG, mmu_enable, enable) \ + | IO_STATE (R_MMU_CONFIG, inv_excp, enable) \ + | IO_STATE (R_MMU_CONFIG, acc_excp, enable) \ + | IO_STATE (R_MMU_CONFIG, we_excp, enable) \ + | IO_STATE (R_MMU_CONFIG, seg_f, page) \ + | IO_STATE (R_MMU_CONFIG, seg_e, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_d, page) \ + | IO_STATE (R_MMU_CONFIG, seg_c, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_b, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_a, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_9, page) \ + | IO_STATE (R_MMU_CONFIG, seg_8, page) \ + | IO_STATE (R_MMU_CONFIG, seg_7, page) \ + | IO_STATE (R_MMU_CONFIG, seg_6, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_5, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_4, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_3, page) \ + | IO_STATE (R_MMU_CONFIG, seg_2, page) \ + | IO_STATE (R_MMU_CONFIG, seg_1, page) \ + | IO_STATE (R_MMU_CONFIG, seg_0, seg), $r0 + move.d $r0, [R_MMU_CONFIG] +#else + ; kseg mappings + move.d IO_FIELD (R_MMU_KBASE_HI, base_e, 8) \ + | IO_FIELD (R_MMU_KBASE_HI, base_c, 4) \ + | IO_FIELD (R_MMU_KBASE_HI, base_b, 0xb), $r0 + move.d $r0, [R_MMU_KBASE_HI] + + ; temporary map of 0x40->0x40 and 0x00->0x00 + move.d IO_FIELD (R_MMU_KBASE_LO, base_4, 4), $r0 + move.d $r0, [R_MMU_KBASE_LO] + + ; mmu enable, segs f,e,c,b,4,0 segment mapped + move.d IO_STATE (R_MMU_CONFIG, mmu_enable, enable) \ + | IO_STATE (R_MMU_CONFIG, inv_excp, enable) \ + | IO_STATE (R_MMU_CONFIG, acc_excp, enable) \ + | IO_STATE (R_MMU_CONFIG, we_excp, enable) \ + | IO_STATE (R_MMU_CONFIG, seg_f, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_e, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_d, page) \ + | IO_STATE (R_MMU_CONFIG, seg_c, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_b, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_a, page) \ + | IO_STATE (R_MMU_CONFIG, seg_9, page) \ + | IO_STATE (R_MMU_CONFIG, seg_8, page) \ + | IO_STATE (R_MMU_CONFIG, seg_7, page) \ + | IO_STATE (R_MMU_CONFIG, seg_6, page) \ + | IO_STATE (R_MMU_CONFIG, seg_5, page) \ + | IO_STATE (R_MMU_CONFIG, seg_4, seg) \ + | IO_STATE (R_MMU_CONFIG, seg_3, page) \ + | IO_STATE (R_MMU_CONFIG, seg_2, page) \ + | IO_STATE (R_MMU_CONFIG, seg_1, page) \ + | IO_STATE (R_MMU_CONFIG, seg_0, seg), $r0 + move.d $r0, [R_MMU_CONFIG] +#endif + + ;; Now we need to sort out the segments and their locations in RAM or + ;; Flash. The image in the Flash (or in DRAM) consists of 3 pieces: + ;; 1) kernel text, 2) kernel data, 3) ROM filesystem image + ;; But the linker has linked the kernel to expect this layout in + ;; DRAM memory: + ;; 1) kernel text, 2) kernel data, 3) kernel BSS + ;; (the location of the ROM filesystem is determined by the krom driver) + ;; If we boot this from Flash, we want to keep the ROM filesystem in + ;; the flash, we want to copy the text and need to copy the data to DRAM. + ;; But if we boot from DRAM, we need to move the ROMFS image + ;; from its position after kernel data, to after kernel BSS, BEFORE the + ;; kernel starts using the BSS area (since its "overlayed" with the ROMFS) + ;; + ;; In both cases, we start in un-cached mode, and need to jump into a + ;; cached PC after we're done fiddling around with the segments. + ;; + ;; arch/etrax100/etrax100.ld sets some symbols that define the start + ;; and end of each segment. + + ;; Check if we start from DRAM or FLASH by testing PC + + move.d $pc,$r0 + and.d 0x7fffffff,$r0 ; get rid of the non-cache bit + cmp.d 0x10000,$r0 ; arbitrary... just something above this code + blo _inflash0 + nop + + jump _inram ; enter cached ram + + ;; Jumpgate for branches. +_inflash0: + jump _inflash + + ;; Put this in a suitable section where we can reclaim storage + ;; after init. + .section ".init.text", "ax" +_inflash: +#ifdef CONFIG_ETRAX_ETHERNET + ;; Start MII clock to make sure it is running when tranceiver is reset + move.d START_ETHERNET_CLOCK, $r0 + move.d $r0, [R_NETWORK_GEN_CONFIG] +#endif + + ;; Set up waitstates etc according to kernel configuration. + move.d CONFIG_ETRAX_DEF_R_WAITSTATES, $r0 + move.d $r0, [R_WAITSTATES] + + move.d CONFIG_ETRAX_DEF_R_BUS_CONFIG, $r0 + move.d $r0, [R_BUS_CONFIG] + + ;; We need to initialze DRAM registers before we start using the DRAM + + cmp.d RAM_INIT_MAGIC, $r8 ; Already initialized? + beq _dram_init_finished + nop + +#include "../lib/dram_init.S" + +_dram_init_finished: + ;; Copy text+data to DRAM + ;; This is fragile - the calculation of r4 as the image size depends + ;; on that the labels below actually are the first and last positions + ;; in the linker-script. + ;; + ;; Then the locating of the cramfs image depends on the aforementioned + ;; image being located in the flash at 0. This is most often not true, + ;; thus the following does not work (normally there is a rescue-block + ;; between the physical start of the flash and the flash-image start, + ;; and when run with compression, the kernel is actually unpacked to + ;; DRAM and we never get here in the first place :)) + + moveq 0, $r0 ; source + move.d text_start, $r1 ; destination + move.d __vmlinux_end, $r2 ; end destination + move.d $r2, $r4 + sub.d $r1, $r4 ; r4=__vmlinux_end in flash, used below +1: move.w [$r0+], $r3 + move.w $r3, [$r1+] + cmp.d $r2, $r1 + blo 1b + nop + + ;; We keep the cramfs in the flash. + ;; There might be none, but that does not matter because + ;; we don't do anything than read some bytes here. + + moveq 0, $r0 + move.d $r0, [romfs_length] ; default if there is no cramfs + + move.d [$r4], $r0 ; cramfs_super.magic + cmp.d CRAMFS_MAGIC, $r0 + bne 1f + nop + move.d [$r4 + 4], $r0 ; cramfs_super.size + move.d $r0, [romfs_length] +#ifdef CONFIG_CRIS_LOW_MAP + add.d 0x50000000, $r4 ; add flash start in virtual memory (cached) +#else + add.d 0xf0000000, $r4 ; add flash start in virtual memory (cached) +#endif + move.d $r4, [romfs_start] +1: + moveq 1, $r0 + move.d $r0, [romfs_in_flash] + + jump _start_it ; enter code, cached this time + +_inram: + ;; Move the ROM fs to after BSS end. This assumes that the cramfs + ;; second longword contains the length of the cramfs + + moveq 0, $r0 + move.d $r0, [romfs_length] ; default if there is no cramfs + + ;; The kernel could have been unpacked to DRAM by the loader, but + ;; the cramfs image could still be in the Flash directly after the + ;; compressed kernel image. The loader passes the address of the + ;; byte succeeding the last compressed byte in the flash in the + ;; register r9 when starting the kernel. Check if r9 points to a + ;; decent cramfs image! + ;; (Notice that if this is not booted from the loader, r9 will be + ;; garbage but we do sanity checks on it, the chance that it points + ;; to a cramfs magic is small.. ) + + cmp.d 0x0ffffff8, $r9 + bhs _no_romfs_in_flash ; r9 points outside the flash area + nop + move.d [$r9], $r0 ; cramfs_super.magic + cmp.d CRAMFS_MAGIC, $r0 + bne _no_romfs_in_flash + nop + move.d [$r9+4], $r0 ; cramfs_super.length + move.d $r0, [romfs_length] +#ifdef CONFIG_CRIS_LOW_MAP + add.d 0x50000000, $r9 ; add flash start in virtual memory (cached) +#else + add.d 0xf0000000, $r9 ; add flash start in virtual memory (cached) +#endif + move.d $r9, [romfs_start] + + moveq 1, $r0 + move.d $r0, [romfs_in_flash] + + jump _start_it ; enter code, cached this time + +_no_romfs_in_flash: + + ;; Check if there is a cramfs (magic value). + ;; Notice that we check for cramfs magic value - which is + ;; the "rom fs" we'll possibly use in 2.4 if not JFFS (which does + ;; not need this mechanism anyway) + + move.d __init_end, $r0; the image will be after the end of init + move.d [$r0], $r1 ; cramfs assumes same endian on host/target + cmp.d CRAMFS_MAGIC, $r1; magic value in cramfs superblock + bne 2f + nop + + ;; Ok. What is its size ? + + move.d [$r0 + 4], $r2 ; cramfs_super.size (again, no need to swapwb) + + ;; We want to copy it to the end of the BSS + + move.d _end, $r1 + + ;; Remember values so cramfs and setup can find this info + + move.d $r1, [romfs_start] ; new romfs location + move.d $r2, [romfs_length] + + ;; We need to copy it backwards, since they can be overlapping + + add.d $r2, $r0 + add.d $r2, $r1 + + ;; Go ahead. Make my loop. + + lsrq 1, $r2 ; size is in bytes, we copy words + +1: move.w [$r0=$r0-2],$r3 + move.w $r3,[$r1=$r1-2] + subq 1, $r2 + bne 1b + nop + +2: + ;; Dont worry that the BSS is tainted. It will be cleared later. + + moveq 0, $r0 + move.d $r0, [romfs_in_flash] + + jump _start_it ; better skip the additional cramfs check below + +_start_it: + + ;; Check if kernel command line is supplied + cmp.d COMMAND_LINE_MAGIC, $r10 + bne no_command_line + nop + + move.d 256, $r13 + move.d cris_command_line, $r10 + or.d 0x80000000, $r11 ; Make it virtual +1: + move.b [$r11+], $r12 + move.b $r12, [$r10+] + subq 1, $r13 + bne 1b + nop + +no_command_line: + + ;; the kernel stack is overlayed with the task structure for each + ;; task. thus the initial kernel stack is in the same page as the + ;; init_task (but starts in the top of the page, size 8192) + move.d init_thread_union + 8192, $sp + move.d ibr_start,$r0 ; this symbol is set by the linker script + move $r0,$ibr + move.d $r0,[etrax_irv] ; set the interrupt base register and pointer + + ;; Clear BSS region, from _bss_start to _end + + move.d __bss_start, $r0 + move.d _end, $r1 +1: clear.d [$r0+] + cmp.d $r1, $r0 + blo 1b + nop + +#ifdef CONFIG_BLK_DEV_ETRAXIDE + ;; disable ATA before enabling it in genconfig below + moveq 0,$r0 + move.d $r0,[R_ATA_CTRL_DATA] + move.d $r0,[R_ATA_TRANSFER_CNT] + move.d $r0,[R_ATA_CONFIG] +#if 0 + move.d R_PORT_G_DATA, $r1 + move.d $r0, [$r1]; assert ATA bus-reset + nop + nop + nop + nop + nop + nop + move.d 0x08000000,$r0 + move.d $r0,[$r1] +#endif +#endif + +#ifdef CONFIG_JULIETTE + ;; configure external DMA channel 0 before enabling it in genconfig + + moveq 0,$r0 + move.d $r0,[R_EXT_DMA_0_ADDR] + ; cnt enable, word size, output, stop, size 0 + move.d IO_STATE (R_EXT_DMA_0_CMD, cnt, enable) \ + | IO_STATE (R_EXT_DMA_0_CMD, rqpol, ahigh) \ + | IO_STATE (R_EXT_DMA_0_CMD, apol, ahigh) \ + | IO_STATE (R_EXT_DMA_0_CMD, rq_ack, burst) \ + | IO_STATE (R_EXT_DMA_0_CMD, wid, word) \ + | IO_STATE (R_EXT_DMA_0_CMD, dir, output) \ + | IO_STATE (R_EXT_DMA_0_CMD, run, stop) \ + | IO_FIELD (R_EXT_DMA_0_CMD, trf_count, 0),$r0 + move.d $r0,[R_EXT_DMA_0_CMD] + + ;; reset dma4 and wait for completion + + moveq IO_STATE (R_DMA_CH4_CMD, cmd, reset),$r0 + move.b $r0,[R_DMA_CH4_CMD] +1: move.b [R_DMA_CH4_CMD],$r0 + and.b IO_MASK (R_DMA_CH4_CMD, cmd),$r0 + cmp.b IO_STATE (R_DMA_CH4_CMD, cmd, reset),$r0 + beq 1b + nop + + ;; reset dma5 and wait for completion + + moveq IO_STATE (R_DMA_CH5_CMD, cmd, reset),$r0 + move.b $r0,[R_DMA_CH5_CMD] +1: move.b [R_DMA_CH5_CMD],$r0 + and.b IO_MASK (R_DMA_CH5_CMD, cmd),$r0 + cmp.b IO_STATE (R_DMA_CH5_CMD, cmd, reset),$r0 + beq 1b + nop +#endif + + ;; Etrax product HW genconfig setup + + moveq 0,$r0 + + ;; Select or disable serial port 2 +#ifdef CONFIG_ETRAX_SERIAL_PORT2 + or.d IO_STATE (R_GEN_CONFIG, ser2, select),$r0 +#else + or.d IO_STATE (R_GEN_CONFIG, ser2, disable),$r0 +#endif + + ;; Init interfaces (disable them). + or.d IO_STATE (R_GEN_CONFIG, scsi0, disable) \ + | IO_STATE (R_GEN_CONFIG, ata, disable) \ + | IO_STATE (R_GEN_CONFIG, par0, disable) \ + | IO_STATE (R_GEN_CONFIG, mio, disable) \ + | IO_STATE (R_GEN_CONFIG, scsi1, disable) \ + | IO_STATE (R_GEN_CONFIG, scsi0w, disable) \ + | IO_STATE (R_GEN_CONFIG, par1, disable) \ + | IO_STATE (R_GEN_CONFIG, ser3, disable) \ + | IO_STATE (R_GEN_CONFIG, mio_w, disable) \ + | IO_STATE (R_GEN_CONFIG, usb1, disable) \ + | IO_STATE (R_GEN_CONFIG, usb2, disable) \ + | IO_STATE (R_GEN_CONFIG, par_w, disable),$r0 + + ;; Init DMA channel muxing (set to unused clients). + or.d IO_STATE (R_GEN_CONFIG, dma2, ata) \ + | IO_STATE (R_GEN_CONFIG, dma3, ata) \ + | IO_STATE (R_GEN_CONFIG, dma4, scsi1) \ + | IO_STATE (R_GEN_CONFIG, dma5, scsi1) \ + | IO_STATE (R_GEN_CONFIG, dma6, unused) \ + | IO_STATE (R_GEN_CONFIG, dma7, unused) \ + | IO_STATE (R_GEN_CONFIG, dma8, usb) \ + | IO_STATE (R_GEN_CONFIG, dma9, usb),$r0 + + +#if defined(CONFIG_ETRAX_DEF_R_PORT_G0_DIR_OUT) + or.d IO_STATE (R_GEN_CONFIG, g0dir, out),$r0 +#endif + +#if defined(CONFIG_ETRAX_DEF_R_PORT_G8_15_DIR_OUT) + or.d IO_STATE (R_GEN_CONFIG, g8_15dir, out),$r0 +#endif +#if defined(CONFIG_ETRAX_DEF_R_PORT_G16_23_DIR_OUT) + or.d IO_STATE (R_GEN_CONFIG, g16_23dir, out),$r0 +#endif + +#if defined(CONFIG_ETRAX_DEF_R_PORT_G24_DIR_OUT) + or.d IO_STATE (R_GEN_CONFIG, g24dir, out),$r0 +#endif + + move.d $r0,[genconfig_shadow] ; init a shadow register of R_GEN_CONFIG + + move.d $r0,[R_GEN_CONFIG] + +#if 0 + moveq 4,$r0 + move.b $r0,[R_DMA_CH6_CMD] ; reset (ser0 dma out) + move.b $r0,[R_DMA_CH7_CMD] ; reset (ser0 dma in) +1: move.b [R_DMA_CH6_CMD],$r0 ; wait for reset cycle to finish + and.b 7,$r0 + cmp.b 4,$r0 + beq 1b + nop +1: move.b [R_DMA_CH7_CMD],$r0 ; wait for reset cycle to finish + and.b 7,$r0 + cmp.b 4,$r0 + beq 1b + nop +#endif + + moveq IO_STATE (R_DMA_CH8_CMD, cmd, reset),$r0 + move.b $r0,[R_DMA_CH8_CMD] ; reset (ser1 dma out) + move.b $r0,[R_DMA_CH9_CMD] ; reset (ser1 dma in) +1: move.b [R_DMA_CH8_CMD],$r0 ; wait for reset cycle to finish + andq IO_MASK (R_DMA_CH8_CMD, cmd),$r0 + cmpq IO_STATE (R_DMA_CH8_CMD, cmd, reset),$r0 + beq 1b + nop +1: move.b [R_DMA_CH9_CMD],$r0 ; wait for reset cycle to finish + andq IO_MASK (R_DMA_CH9_CMD, cmd),$r0 + cmpq IO_STATE (R_DMA_CH9_CMD, cmd, reset),$r0 + beq 1b + nop + + ;; setup port PA and PB default initial directions and data + ;; including their shadow registers + + move.b CONFIG_ETRAX_DEF_R_PORT_PA_DIR,$r0 +#if defined(CONFIG_BLUETOOTH) && defined(CONFIG_BLUETOOTH_RESET_PA7) + or.b IO_STATE (R_PORT_PA_DIR, dir7, output),$r0 +#endif + move.b $r0,[port_pa_dir_shadow] + move.b $r0,[R_PORT_PA_DIR] + move.b CONFIG_ETRAX_DEF_R_PORT_PA_DATA,$r0 +#if defined(CONFIG_BLUETOOTH) && defined(CONFIG_BLUETOOTH_RESET_PA7) +#if defined(CONFIG_BLUETOOTH_RESET_ACTIVE_HIGH) + and.b ~(1 << 7),$r0 +#else + or.b (1 << 7),$r0 +#endif +#endif + move.b $r0,[port_pa_data_shadow] + move.b $r0,[R_PORT_PA_DATA] + + move.b CONFIG_ETRAX_DEF_R_PORT_PB_CONFIG,$r0 + move.b $r0,[port_pb_config_shadow] + move.b $r0,[R_PORT_PB_CONFIG] + move.b CONFIG_ETRAX_DEF_R_PORT_PB_DIR,$r0 +#if defined(CONFIG_BLUETOOTH) && defined(CONFIG_BLUETOOTH_RESET_PB5) + or.b IO_STATE (R_PORT_PB_DIR, dir5, output),$r0 +#endif + move.b $r0,[port_pb_dir_shadow] + move.b $r0,[R_PORT_PB_DIR] + move.b CONFIG_ETRAX_DEF_R_PORT_PB_DATA,$r0 +#if defined(CONFIG_BLUETOOTH) && defined(CONFIG_BLUETOOTH_RESET_PB5) +#if defined(CONFIG_BLUETOOTH_RESET_ACTIVE_HIGH) + and.b ~(1 << 5),$r0 +#else + or.b (1 << 5),$r0 +#endif +#endif + move.b $r0,[port_pb_data_shadow] + move.b $r0,[R_PORT_PB_DATA] + + moveq 0, $r0 + move.d $r0,[port_pb_i2c_shadow] + move.d $r0, [R_PORT_PB_I2C] + + moveq 0,$r0 +#if defined(CONFIG_BLUETOOTH) && defined(CONFIG_BLUETOOTH_RESET_G10) +#if defined(CONFIG_BLUETOOTH_RESET_ACTIVE_HIGH) + and.d ~(1 << 10),$r0 +#else + or.d (1 << 10),$r0 +#endif +#endif +#if defined(CONFIG_BLUETOOTH) && defined(CONFIG_BLUETOOTH_RESET_G11) +#if defined(CONFIG_BLUETOOTH_RESET_ACTIVE_HIGH) + and.d ~(1 << 11),$r0 +#else + or.d (1 << 11),$r0 +#endif +#endif + move.d $r0,[port_g_data_shadow] + move.d $r0,[R_PORT_G_DATA] + + ;; setup the serial port 0 at 115200 baud for debug purposes + + moveq IO_STATE (R_SERIAL0_XOFF, tx_stop, enable) \ + | IO_STATE (R_SERIAL0_XOFF, auto_xoff, disable) \ + | IO_FIELD (R_SERIAL0_XOFF, xoff_char, 0),$r0 + move.d $r0,[R_SERIAL0_XOFF] + + ; 115.2kbaud for both transmit and receive + move.b IO_STATE (R_SERIAL0_BAUD, tr_baud, c115k2Hz) \ + | IO_STATE (R_SERIAL0_BAUD, rec_baud, c115k2Hz),$r0 + move.b $r0,[R_SERIAL0_BAUD] + + ; Set up and enable the serial0 receiver. + move.b IO_STATE (R_SERIAL0_REC_CTRL, dma_err, stop) \ + | IO_STATE (R_SERIAL0_REC_CTRL, rec_enable, enable) \ + | IO_STATE (R_SERIAL0_REC_CTRL, rts_, active) \ + | IO_STATE (R_SERIAL0_REC_CTRL, sampling, middle) \ + | IO_STATE (R_SERIAL0_REC_CTRL, rec_stick_par, normal) \ + | IO_STATE (R_SERIAL0_REC_CTRL, rec_par, even) \ + | IO_STATE (R_SERIAL0_REC_CTRL, rec_par_en, disable) \ + | IO_STATE (R_SERIAL0_REC_CTRL, rec_bitnr, rec_8bit),$r0 + move.b $r0,[R_SERIAL0_REC_CTRL] + + ; Set up and enable the serial0 transmitter. + move.b IO_FIELD (R_SERIAL0_TR_CTRL, txd, 0) \ + | IO_STATE (R_SERIAL0_TR_CTRL, tr_enable, enable) \ + | IO_STATE (R_SERIAL0_TR_CTRL, auto_cts, disabled) \ + | IO_STATE (R_SERIAL0_TR_CTRL, stop_bits, one_bit) \ + | IO_STATE (R_SERIAL0_TR_CTRL, tr_stick_par, normal) \ + | IO_STATE (R_SERIAL0_TR_CTRL, tr_par, even) \ + | IO_STATE (R_SERIAL0_TR_CTRL, tr_par_en, disable) \ + | IO_STATE (R_SERIAL0_TR_CTRL, tr_bitnr, tr_8bit),$r0 + move.b $r0,[R_SERIAL0_TR_CTRL] + + ;; setup the serial port 1 at 115200 baud for debug purposes + + moveq IO_STATE (R_SERIAL1_XOFF, tx_stop, enable) \ + | IO_STATE (R_SERIAL1_XOFF, auto_xoff, disable) \ + | IO_FIELD (R_SERIAL1_XOFF, xoff_char, 0),$r0 + move.d $r0,[R_SERIAL1_XOFF] + + ; 115.2kbaud for both transmit and receive + move.b IO_STATE (R_SERIAL1_BAUD, tr_baud, c115k2Hz) \ + | IO_STATE (R_SERIAL1_BAUD, rec_baud, c115k2Hz),$r0 + move.b $r0,[R_SERIAL1_BAUD] + + ; Set up and enable the serial1 receiver. + move.b IO_STATE (R_SERIAL1_REC_CTRL, dma_err, stop) \ + | IO_STATE (R_SERIAL1_REC_CTRL, rec_enable, enable) \ + | IO_STATE (R_SERIAL1_REC_CTRL, rts_, active) \ + | IO_STATE (R_SERIAL1_REC_CTRL, sampling, middle) \ + | IO_STATE (R_SERIAL1_REC_CTRL, rec_stick_par, normal) \ + | IO_STATE (R_SERIAL1_REC_CTRL, rec_par, even) \ + | IO_STATE (R_SERIAL1_REC_CTRL, rec_par_en, disable) \ + | IO_STATE (R_SERIAL1_REC_CTRL, rec_bitnr, rec_8bit),$r0 + move.b $r0,[R_SERIAL1_REC_CTRL] + + ; Set up and enable the serial1 transmitter. + move.b IO_FIELD (R_SERIAL1_TR_CTRL, txd, 0) \ + | IO_STATE (R_SERIAL1_TR_CTRL, tr_enable, enable) \ + | IO_STATE (R_SERIAL1_TR_CTRL, auto_cts, disabled) \ + | IO_STATE (R_SERIAL1_TR_CTRL, stop_bits, one_bit) \ + | IO_STATE (R_SERIAL1_TR_CTRL, tr_stick_par, normal) \ + | IO_STATE (R_SERIAL1_TR_CTRL, tr_par, even) \ + | IO_STATE (R_SERIAL1_TR_CTRL, tr_par_en, disable) \ + | IO_STATE (R_SERIAL1_TR_CTRL, tr_bitnr, tr_8bit),$r0 + move.b $r0,[R_SERIAL1_TR_CTRL] + +#ifdef CONFIG_ETRAX_SERIAL_PORT2 + ;; setup the serial port 2 at 115200 baud for debug purposes + + moveq IO_STATE (R_SERIAL2_XOFF, tx_stop, enable) \ + | IO_STATE (R_SERIAL2_XOFF, auto_xoff, disable) \ + | IO_FIELD (R_SERIAL2_XOFF, xoff_char, 0),$r0 + move.d $r0,[R_SERIAL2_XOFF] + + ; 115.2kbaud for both transmit and receive + move.b IO_STATE (R_SERIAL2_BAUD, tr_baud, c115k2Hz) \ + | IO_STATE (R_SERIAL2_BAUD, rec_baud, c115k2Hz),$r0 + move.b $r0,[R_SERIAL2_BAUD] + + ; Set up and enable the serial2 receiver. + move.b IO_STATE (R_SERIAL2_REC_CTRL, dma_err, stop) \ + | IO_STATE (R_SERIAL2_REC_CTRL, rec_enable, enable) \ + | IO_STATE (R_SERIAL2_REC_CTRL, rts_, active) \ + | IO_STATE (R_SERIAL2_REC_CTRL, sampling, middle) \ + | IO_STATE (R_SERIAL2_REC_CTRL, rec_stick_par, normal) \ + | IO_STATE (R_SERIAL2_REC_CTRL, rec_par, even) \ + | IO_STATE (R_SERIAL2_REC_CTRL, rec_par_en, disable) \ + | IO_STATE (R_SERIAL2_REC_CTRL, rec_bitnr, rec_8bit),$r0 + move.b $r0,[R_SERIAL2_REC_CTRL] + + ; Set up and enable the serial2 transmitter. + move.b IO_FIELD (R_SERIAL2_TR_CTRL, txd, 0) \ + | IO_STATE (R_SERIAL2_TR_CTRL, tr_enable, enable) \ + | IO_STATE (R_SERIAL2_TR_CTRL, auto_cts, disabled) \ + | IO_STATE (R_SERIAL2_TR_CTRL, stop_bits, one_bit) \ + | IO_STATE (R_SERIAL2_TR_CTRL, tr_stick_par, normal) \ + | IO_STATE (R_SERIAL2_TR_CTRL, tr_par, even) \ + | IO_STATE (R_SERIAL2_TR_CTRL, tr_par_en, disable) \ + | IO_STATE (R_SERIAL2_TR_CTRL, tr_bitnr, tr_8bit),$r0 + move.b $r0,[R_SERIAL2_TR_CTRL] +#endif + +#ifdef CONFIG_ETRAX_SERIAL_PORT3 + ;; setup the serial port 3 at 115200 baud for debug purposes + + moveq IO_STATE (R_SERIAL3_XOFF, tx_stop, enable) \ + | IO_STATE (R_SERIAL3_XOFF, auto_xoff, disable) \ + | IO_FIELD (R_SERIAL3_XOFF, xoff_char, 0),$r0 + move.d $r0,[R_SERIAL3_XOFF] + + ; 115.2kbaud for both transmit and receive + move.b IO_STATE (R_SERIAL3_BAUD, tr_baud, c115k2Hz) \ + | IO_STATE (R_SERIAL3_BAUD, rec_baud, c115k2Hz),$r0 + move.b $r0,[R_SERIAL3_BAUD] + + ; Set up and enable the serial3 receiver. + move.b IO_STATE (R_SERIAL3_REC_CTRL, dma_err, stop) \ + | IO_STATE (R_SERIAL3_REC_CTRL, rec_enable, enable) \ + | IO_STATE (R_SERIAL3_REC_CTRL, rts_, active) \ + | IO_STATE (R_SERIAL3_REC_CTRL, sampling, middle) \ + | IO_STATE (R_SERIAL3_REC_CTRL, rec_stick_par, normal) \ + | IO_STATE (R_SERIAL3_REC_CTRL, rec_par, even) \ + | IO_STATE (R_SERIAL3_REC_CTRL, rec_par_en, disable) \ + | IO_STATE (R_SERIAL3_REC_CTRL, rec_bitnr, rec_8bit),$r0 + move.b $r0,[R_SERIAL3_REC_CTRL] + + ; Set up and enable the serial3 transmitter. + move.b IO_FIELD (R_SERIAL3_TR_CTRL, txd, 0) \ + | IO_STATE (R_SERIAL3_TR_CTRL, tr_enable, enable) \ + | IO_STATE (R_SERIAL3_TR_CTRL, auto_cts, disabled) \ + | IO_STATE (R_SERIAL3_TR_CTRL, stop_bits, one_bit) \ + | IO_STATE (R_SERIAL3_TR_CTRL, tr_stick_par, normal) \ + | IO_STATE (R_SERIAL3_TR_CTRL, tr_par, even) \ + | IO_STATE (R_SERIAL3_TR_CTRL, tr_par_en, disable) \ + | IO_STATE (R_SERIAL3_TR_CTRL, tr_bitnr, tr_8bit),$r0 + move.b $r0,[R_SERIAL3_TR_CTRL] +#endif + + jump start_kernel ; jump into the C-function start_kernel in init/main.c + + .data +etrax_irv: + .dword 0 +romfs_start: + .dword 0 +romfs_length: + .dword 0 +romfs_in_flash: + .dword 0 + + ;; put some special pages at the beginning of the kernel aligned + ;; to page boundaries - the kernel cannot start until after this + +#ifdef CONFIG_CRIS_LOW_MAP +swapper_pg_dir = 0x60002000 +#else +swapper_pg_dir = 0xc0002000 +#endif + + .section ".init.data", "aw" +#include "../lib/hw_settings.S" diff --git a/arch/cris/arch-v10/kernel/io_interface_mux.c b/arch/cris/arch-v10/kernel/io_interface_mux.c new file mode 100644 index 000000000..ad64cd1c8 --- /dev/null +++ b/arch/cris/arch-v10/kernel/io_interface_mux.c @@ -0,0 +1,1182 @@ +/* IO interface mux allocator for ETRAX100LX. + * Copyright 2004-2007, Axis Communications AB + */ + + +/* C.f. ETRAX100LX Designer's Reference chapter 19.9 */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> + +#include <arch/svinto.h> +#include <asm/io.h> +#include <arch/io_interface_mux.h> +#include <arch/system.h> + + +#define DBG(s) + +/* Macro to access ETRAX 100 registers */ +#define SETS(var, reg, field, val) var = (var & ~IO_MASK_(reg##_, field##_)) | \ + IO_STATE_(reg##_, field##_, _##val) + +enum io_if_group { + group_a = (1<<0), + group_b = (1<<1), + group_c = (1<<2), + group_d = (1<<3), + group_e = (1<<4), + group_f = (1<<5) +}; + +struct watcher +{ + void (*notify)(const unsigned int gpio_in_available, + const unsigned int gpio_out_available, + const unsigned char pa_available, + const unsigned char pb_available); + struct watcher *next; +}; + + +struct if_group +{ + enum io_if_group group; + /* name - the name of the group 'A' to 'F' */ + char *name; + /* used - a bit mask of all pins in the group in the order listed + * in the tables in 19.9.1 to 19.9.6. Note that no + * distinction is made between in, out and in/out pins. */ + unsigned int used; +}; + + +struct interface +{ + enum cris_io_interface ioif; + /* name - the name of the interface */ + char *name; + /* groups - OR'ed together io_if_group flags describing what pin groups + * the interface uses pins in. */ + unsigned char groups; + /* used - set when the interface is allocated. */ + unsigned char used; + char *owner; + /* group_a through group_f - bit masks describing what pins in the + * pin groups the interface uses. */ + unsigned int group_a; + unsigned int group_b; + unsigned int group_c; + unsigned int group_d; + unsigned int group_e; + unsigned int group_f; + + /* gpio_g_in, gpio_g_out, gpio_b - bit masks telling what pins in the + * GPIO ports the interface uses. This could be reconstucted using + * the group_X masks and a table of what pins the GPIO ports use, + * but that would be messy. */ + unsigned int gpio_g_in; + unsigned int gpio_g_out; + unsigned char gpio_b; +}; + +static struct if_group if_groups[6] = { + { + .group = group_a, + .name = "A", + .used = 0, + }, + { + .group = group_b, + .name = "B", + .used = 0, + }, + { + .group = group_c, + .name = "C", + .used = 0, + }, + { + .group = group_d, + .name = "D", + .used = 0, + }, + { + .group = group_e, + .name = "E", + .used = 0, + }, + { + .group = group_f, + .name = "F", + .used = 0, + } +}; + +/* The order in the array must match the order of enum + * cris_io_interface in io_interface_mux.h */ +static struct interface interfaces[] = { + /* Begin Non-multiplexed interfaces */ + { + .ioif = if_eth, + .name = "ethernet", + .groups = 0, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0, + .gpio_g_out = 0, + .gpio_b = 0 + }, + { + .ioif = if_serial_0, + .name = "serial_0", + .groups = 0, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0, + .gpio_g_out = 0, + .gpio_b = 0 + }, + /* End Non-multiplexed interfaces */ + { + .ioif = if_serial_1, + .name = "serial_1", + .groups = group_e, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0x0f, + .group_f = 0, + + .gpio_g_in = 0x00000000, + .gpio_g_out = 0x00000000, + .gpio_b = 0x00 + }, + { + .ioif = if_serial_2, + .name = "serial_2", + .groups = group_b, + + .group_a = 0, + .group_b = 0x0f, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x000000c0, + .gpio_g_out = 0x000000c0, + .gpio_b = 0x00 + }, + { + .ioif = if_serial_3, + .name = "serial_3", + .groups = group_c, + + .group_a = 0, + .group_b = 0, + .group_c = 0x0f, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0xc0000000, + .gpio_g_out = 0xc0000000, + .gpio_b = 0x00 + }, + { + .ioif = if_sync_serial_1, + .name = "sync_serial_1", + .groups = group_e | group_f, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0x0f, + .group_f = 0x10, + + .gpio_g_in = 0x00000000, + .gpio_g_out = 0x00000000, + .gpio_b = 0x10 + }, + { + .ioif = if_sync_serial_3, + .name = "sync_serial_3", + .groups = group_c | group_f, + + .group_a = 0, + .group_b = 0, + .group_c = 0x0f, + .group_d = 0, + .group_e = 0, + .group_f = 0x80, + + .gpio_g_in = 0xc0000000, + .gpio_g_out = 0xc0000000, + .gpio_b = 0x80 + }, + { + .ioif = if_shared_ram, + .name = "shared_ram", + .groups = group_a, + + .group_a = 0x7f8ff, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x0000ff3e, + .gpio_g_out = 0x0000ff38, + .gpio_b = 0x00 + }, + { + .ioif = if_shared_ram_w, + .name = "shared_ram_w", + .groups = group_a | group_d, + + .group_a = 0x7f8ff, + .group_b = 0, + .group_c = 0, + .group_d = 0xff, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x00ffff3e, + .gpio_g_out = 0x00ffff38, + .gpio_b = 0x00 + }, + { + .ioif = if_par_0, + .name = "par_0", + .groups = group_a, + + .group_a = 0x7fbff, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x0000ff3e, + .gpio_g_out = 0x0000ff3e, + .gpio_b = 0x00 + }, + { + .ioif = if_par_1, + .name = "par_1", + .groups = group_d, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0x7feff, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x3eff0000, + .gpio_g_out = 0x3eff0000, + .gpio_b = 0x00 + }, + { + .ioif = if_par_w, + .name = "par_w", + .groups = group_a | group_d, + + .group_a = 0x7fbff, + .group_b = 0, + .group_c = 0, + .group_d = 0xff, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x00ffff3e, + .gpio_g_out = 0x00ffff3e, + .gpio_b = 0x00 + }, + { + .ioif = if_scsi8_0, + .name = "scsi8_0", + .groups = group_a | group_b | group_f, + + .group_a = 0x7ffff, + .group_b = 0x0f, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0x10, + + .gpio_g_in = 0x0000ffff, + .gpio_g_out = 0x0000ffff, + .gpio_b = 0x10 + }, + { + .ioif = if_scsi8_1, + .name = "scsi8_1", + .groups = group_c | group_d | group_f, + + .group_a = 0, + .group_b = 0, + .group_c = 0x0f, + .group_d = 0x7ffff, + .group_e = 0, + .group_f = 0x80, + + .gpio_g_in = 0xffff0000, + .gpio_g_out = 0xffff0000, + .gpio_b = 0x80 + }, + { + .ioif = if_scsi_w, + .name = "scsi_w", + .groups = group_a | group_b | group_d | group_f, + + .group_a = 0x7ffff, + .group_b = 0x0f, + .group_c = 0, + .group_d = 0x601ff, + .group_e = 0, + .group_f = 0x90, + + .gpio_g_in = 0x01ffffff, + .gpio_g_out = 0x07ffffff, + .gpio_b = 0x80 + }, + { + .ioif = if_ata, + .name = "ata", + .groups = group_a | group_b | group_c | group_d, + + .group_a = 0x7ffff, + .group_b = 0x0f, + .group_c = 0x0f, + .group_d = 0x7cfff, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0xf9ffffff, + .gpio_g_out = 0xffffffff, + .gpio_b = 0x80 + }, + { + .ioif = if_csp, + .name = "csp", + .groups = group_f, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0xfc, + + .gpio_g_in = 0x00000000, + .gpio_g_out = 0x00000000, + .gpio_b = 0xfc + }, + { + .ioif = if_i2c, + .name = "i2c", + .groups = group_f, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0x03, + + .gpio_g_in = 0x00000000, + .gpio_g_out = 0x00000000, + .gpio_b = 0x03 + }, + { + .ioif = if_usb_1, + .name = "usb_1", + .groups = group_e | group_f, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0x0f, + .group_f = 0x2c, + + .gpio_g_in = 0x00000000, + .gpio_g_out = 0x00000000, + .gpio_b = 0x2c + }, + { + .ioif = if_usb_2, + .name = "usb_2", + .groups = group_d, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0x33e00, + .group_f = 0, + + .gpio_g_in = 0x3e000000, + .gpio_g_out = 0x0c000000, + .gpio_b = 0x00 + }, + /* GPIO pins */ + { + .ioif = if_gpio_grp_a, + .name = "gpio_a", + .groups = group_a, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x0000ff3f, + .gpio_g_out = 0x0000ff3f, + .gpio_b = 0x00 + }, + { + .ioif = if_gpio_grp_b, + .name = "gpio_b", + .groups = group_b, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x000000c0, + .gpio_g_out = 0x000000c0, + .gpio_b = 0x00 + }, + { + .ioif = if_gpio_grp_c, + .name = "gpio_c", + .groups = group_c, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0xc0000000, + .gpio_g_out = 0xc0000000, + .gpio_b = 0x00 + }, + { + .ioif = if_gpio_grp_d, + .name = "gpio_d", + .groups = group_d, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x3fff0000, + .gpio_g_out = 0x3fff0000, + .gpio_b = 0x00 + }, + { + .ioif = if_gpio_grp_e, + .name = "gpio_e", + .groups = group_e, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x00000000, + .gpio_g_out = 0x00000000, + .gpio_b = 0x00 + }, + { + .ioif = if_gpio_grp_f, + .name = "gpio_f", + .groups = group_f, + + .group_a = 0, + .group_b = 0, + .group_c = 0, + .group_d = 0, + .group_e = 0, + .group_f = 0, + + .gpio_g_in = 0x00000000, + .gpio_g_out = 0x00000000, + .gpio_b = 0xff + } + /* Array end */ +}; + +static struct watcher *watchers = NULL; + +/* The pins that are free to use in the GPIO ports. */ +static unsigned int gpio_in_pins = 0xffffffff; +static unsigned int gpio_out_pins = 0xffffffff; +static unsigned char gpio_pb_pins = 0xff; +static unsigned char gpio_pa_pins = 0xff; + +/* Identifiers for the owners of the GPIO pins. */ +static enum cris_io_interface gpio_pa_owners[8]; +static enum cris_io_interface gpio_pb_owners[8]; +static enum cris_io_interface gpio_pg_owners[32]; + +static int cris_io_interface_init(void); + +static unsigned char clear_group_from_set(const unsigned char groups, struct if_group *group) +{ + return (groups & ~group->group); +} + + +static struct if_group *get_group(const unsigned char groups) +{ + int i; + for (i = 0; i < ARRAY_SIZE(if_groups); i++) { + if (groups & if_groups[i].group) { + return &if_groups[i]; + } + } + return NULL; +} + + +static void notify_watchers(void) +{ + struct watcher *w = watchers; + + DBG(printk("io_interface_mux: notifying watchers\n")); + + while (NULL != w) { + w->notify((const unsigned int)gpio_in_pins, + (const unsigned int)gpio_out_pins, + (const unsigned char)gpio_pa_pins, + (const unsigned char)gpio_pb_pins); + w = w->next; + } +} + + +int cris_request_io_interface(enum cris_io_interface ioif, const char *device_id) +{ + int set_gen_config = 0; + int set_gen_config_ii = 0; + unsigned long int gens; + unsigned long int gens_ii; + struct if_group *grp; + unsigned char group_set; + unsigned long flags; + int res = 0; + + (void)cris_io_interface_init(); + + DBG(printk("cris_request_io_interface(%d, \"%s\")\n", ioif, device_id)); + + if ((ioif >= if_max_interfaces) || (ioif < 0)) { + printk(KERN_CRIT "cris_request_io_interface: Bad interface " + "%u submitted for %s\n", + ioif, + device_id); + return -EINVAL; + } + + local_irq_save(flags); + + if (interfaces[ioif].used) { + printk(KERN_CRIT "cris_io_interface: Cannot allocate interface " + "%s for %s, in use by %s\n", + interfaces[ioif].name, + device_id, + interfaces[ioif].owner); + res = -EBUSY; + goto exit; + } + + /* Check that all required pins in the used groups are free + * before allocating. */ + group_set = interfaces[ioif].groups; + while (NULL != (grp = get_group(group_set))) { + unsigned int if_group_use = 0; + + switch (grp->group) { + case group_a: + if_group_use = interfaces[ioif].group_a; + break; + case group_b: + if_group_use = interfaces[ioif].group_b; + break; + case group_c: + if_group_use = interfaces[ioif].group_c; + break; + case group_d: + if_group_use = interfaces[ioif].group_d; + break; + case group_e: + if_group_use = interfaces[ioif].group_e; + break; + case group_f: + if_group_use = interfaces[ioif].group_f; + break; + default: + BUG_ON(1); + } + + if (if_group_use & grp->used) { + printk(KERN_INFO "cris_request_io_interface: group " + "%s needed by %s not available\n", + grp->name, interfaces[ioif].name); + res = -EBUSY; + goto exit; + } + + group_set = clear_group_from_set(group_set, grp); + } + + /* Are the required GPIO pins available too? */ + if (((interfaces[ioif].gpio_g_in & gpio_in_pins) != + interfaces[ioif].gpio_g_in) || + ((interfaces[ioif].gpio_g_out & gpio_out_pins) != + interfaces[ioif].gpio_g_out) || + ((interfaces[ioif].gpio_b & gpio_pb_pins) != + interfaces[ioif].gpio_b)) { + printk(KERN_CRIT "cris_request_io_interface: Could not get " + "required pins for interface %u\n", ioif); + res = -EBUSY; + goto exit; + } + + /* Check which registers need to be reconfigured. */ + gens = genconfig_shadow; + gens_ii = gen_config_ii_shadow; + + set_gen_config = 1; + switch (ioif) + { + /* Begin Non-multiplexed interfaces */ + case if_eth: + /* fall through */ + case if_serial_0: + set_gen_config = 0; + break; + /* End Non-multiplexed interfaces */ + case if_serial_1: + set_gen_config_ii = 1; + SETS(gens_ii, R_GEN_CONFIG_II, sermode1, async); + break; + case if_serial_2: + SETS(gens, R_GEN_CONFIG, ser2, select); + break; + case if_serial_3: + SETS(gens, R_GEN_CONFIG, ser3, select); + set_gen_config_ii = 1; + SETS(gens_ii, R_GEN_CONFIG_II, sermode3, async); + break; + case if_sync_serial_1: + set_gen_config_ii = 1; + SETS(gens_ii, R_GEN_CONFIG_II, sermode1, sync); + break; + case if_sync_serial_3: + SETS(gens, R_GEN_CONFIG, ser3, select); + set_gen_config_ii = 1; + SETS(gens_ii, R_GEN_CONFIG_II, sermode3, sync); + break; + case if_shared_ram: + SETS(gens, R_GEN_CONFIG, mio, select); + break; + case if_shared_ram_w: + SETS(gens, R_GEN_CONFIG, mio_w, select); + break; + case if_par_0: + SETS(gens, R_GEN_CONFIG, par0, select); + break; + case if_par_1: + SETS(gens, R_GEN_CONFIG, par1, select); + break; + case if_par_w: + SETS(gens, R_GEN_CONFIG, par0, select); + SETS(gens, R_GEN_CONFIG, par_w, select); + break; + case if_scsi8_0: + SETS(gens, R_GEN_CONFIG, scsi0, select); + break; + case if_scsi8_1: + SETS(gens, R_GEN_CONFIG, scsi1, select); + break; + case if_scsi_w: + SETS(gens, R_GEN_CONFIG, scsi0, select); + SETS(gens, R_GEN_CONFIG, scsi0w, select); + break; + case if_ata: + SETS(gens, R_GEN_CONFIG, ata, select); + break; + case if_csp: + /* fall through */ + case if_i2c: + set_gen_config = 0; + break; + case if_usb_1: + SETS(gens, R_GEN_CONFIG, usb1, select); + break; + case if_usb_2: + SETS(gens, R_GEN_CONFIG, usb2, select); + break; + case if_gpio_grp_a: + /* GPIO groups are only accounted, don't do configuration changes. */ + /* fall through */ + case if_gpio_grp_b: + /* fall through */ + case if_gpio_grp_c: + /* fall through */ + case if_gpio_grp_d: + /* fall through */ + case if_gpio_grp_e: + /* fall through */ + case if_gpio_grp_f: + set_gen_config = 0; + break; + default: + printk(KERN_INFO "cris_request_io_interface: Bad interface " + "%u submitted for %s\n", + ioif, device_id); + res = -EBUSY; + goto exit; + } + + /* All needed I/O pins and pin groups are free, allocate. */ + group_set = interfaces[ioif].groups; + while (NULL != (grp = get_group(group_set))) { + unsigned int if_group_use = 0; + + switch (grp->group) { + case group_a: + if_group_use = interfaces[ioif].group_a; + break; + case group_b: + if_group_use = interfaces[ioif].group_b; + break; + case group_c: + if_group_use = interfaces[ioif].group_c; + break; + case group_d: + if_group_use = interfaces[ioif].group_d; + break; + case group_e: + if_group_use = interfaces[ioif].group_e; + break; + case group_f: + if_group_use = interfaces[ioif].group_f; + break; + default: + BUG_ON(1); + } + grp->used |= if_group_use; + + group_set = clear_group_from_set(group_set, grp); + } + + interfaces[ioif].used = 1; + interfaces[ioif].owner = (char*)device_id; + + if (set_gen_config) { + volatile int i; + genconfig_shadow = gens; + *R_GEN_CONFIG = genconfig_shadow; + /* Wait 12 cycles before doing any DMA command */ + for(i = 6; i > 0; i--) + nop(); + } + if (set_gen_config_ii) { + gen_config_ii_shadow = gens_ii; + *R_GEN_CONFIG_II = gen_config_ii_shadow; + } + + DBG(printk(KERN_DEBUG "GPIO pins: available before: " + "g_in=0x%08x g_out=0x%08x pb=0x%02x\n", + gpio_in_pins, gpio_out_pins, gpio_pb_pins)); + DBG(printk(KERN_DEBUG + "grabbing pins: g_in=0x%08x g_out=0x%08x pb=0x%02x\n", + interfaces[ioif].gpio_g_in, + interfaces[ioif].gpio_g_out, + interfaces[ioif].gpio_b)); + + gpio_in_pins &= ~interfaces[ioif].gpio_g_in; + gpio_out_pins &= ~interfaces[ioif].gpio_g_out; + gpio_pb_pins &= ~interfaces[ioif].gpio_b; + + DBG(printk(KERN_DEBUG "GPIO pins: available after: " + "g_in=0x%08x g_out=0x%08x pb=0x%02x\n", + gpio_in_pins, gpio_out_pins, gpio_pb_pins)); + +exit: + local_irq_restore(flags); + if (res == 0) + notify_watchers(); + return res; +} + + +void cris_free_io_interface(enum cris_io_interface ioif) +{ + struct if_group *grp; + unsigned char group_set; + unsigned long flags; + + (void)cris_io_interface_init(); + + if ((ioif >= if_max_interfaces) || (ioif < 0)) { + printk(KERN_CRIT "cris_free_io_interface: Bad interface %u\n", + ioif); + return; + } + local_irq_save(flags); + if (!interfaces[ioif].used) { + printk(KERN_CRIT "cris_free_io_interface: Freeing free interface %u\n", + ioif); + local_irq_restore(flags); + return; + } + group_set = interfaces[ioif].groups; + while (NULL != (grp = get_group(group_set))) { + unsigned int if_group_use = 0; + + switch (grp->group) { + case group_a: + if_group_use = interfaces[ioif].group_a; + break; + case group_b: + if_group_use = interfaces[ioif].group_b; + break; + case group_c: + if_group_use = interfaces[ioif].group_c; + break; + case group_d: + if_group_use = interfaces[ioif].group_d; + break; + case group_e: + if_group_use = interfaces[ioif].group_e; + break; + case group_f: + if_group_use = interfaces[ioif].group_f; + break; + default: + BUG_ON(1); + } + + if ((grp->used & if_group_use) != if_group_use) + BUG_ON(1); + grp->used = grp->used & ~if_group_use; + + group_set = clear_group_from_set(group_set, grp); + } + interfaces[ioif].used = 0; + interfaces[ioif].owner = NULL; + + DBG(printk("GPIO pins: available before: g_in=0x%08x g_out=0x%08x pb=0x%02x\n", + gpio_in_pins, gpio_out_pins, gpio_pb_pins)); + DBG(printk("freeing pins: g_in=0x%08x g_out=0x%08x pb=0x%02x\n", + interfaces[ioif].gpio_g_in, + interfaces[ioif].gpio_g_out, + interfaces[ioif].gpio_b)); + + gpio_in_pins |= interfaces[ioif].gpio_g_in; + gpio_out_pins |= interfaces[ioif].gpio_g_out; + gpio_pb_pins |= interfaces[ioif].gpio_b; + + DBG(printk("GPIO pins: available after: g_in=0x%08x g_out=0x%08x pb=0x%02x\n", + gpio_in_pins, gpio_out_pins, gpio_pb_pins)); + + local_irq_restore(flags); + + notify_watchers(); +} + +/* Create a bitmask from bit 0 (inclusive) to bit stop_bit + (non-inclusive). stop_bit == 0 returns 0x0 */ +static inline unsigned int create_mask(const unsigned stop_bit) +{ + /* Avoid overflow */ + if (stop_bit >= 32) { + return 0xffffffff; + } + return (1<<stop_bit)-1; +} + + +/* port can be 'a', 'b' or 'g' */ +int cris_io_interface_allocate_pins(const enum cris_io_interface ioif, + const char port, + const unsigned start_bit, + const unsigned stop_bit) +{ + unsigned int i; + unsigned int mask = 0; + unsigned int tmp_mask; + unsigned long int flags; + enum cris_io_interface *owners; + + (void)cris_io_interface_init(); + + DBG(printk("cris_io_interface_allocate_pins: if=%d port=%c start=%u stop=%u\n", + ioif, port, start_bit, stop_bit)); + + if (!((start_bit <= stop_bit) && + ((((port == 'a') || (port == 'b')) && (stop_bit < 8)) || + ((port == 'g') && (stop_bit < 32))))) { + return -EINVAL; + } + + mask = create_mask(stop_bit + 1); + tmp_mask = create_mask(start_bit); + mask &= ~tmp_mask; + + DBG(printk("cris_io_interface_allocate_pins: port=%c start=%u stop=%u mask=0x%08x\n", + port, start_bit, stop_bit, mask)); + + local_irq_save(flags); + + switch (port) { + case 'a': + if ((gpio_pa_pins & mask) != mask) { + local_irq_restore(flags); + return -EBUSY; + } + owners = gpio_pa_owners; + gpio_pa_pins &= ~mask; + break; + case 'b': + if ((gpio_pb_pins & mask) != mask) { + local_irq_restore(flags); + return -EBUSY; + } + owners = gpio_pb_owners; + gpio_pb_pins &= ~mask; + break; + case 'g': + if (((gpio_in_pins & mask) != mask) || + ((gpio_out_pins & mask) != mask)) { + local_irq_restore(flags); + return -EBUSY; + } + owners = gpio_pg_owners; + gpio_in_pins &= ~mask; + gpio_out_pins &= ~mask; + break; + default: + local_irq_restore(flags); + return -EINVAL; + } + + for (i = start_bit; i <= stop_bit; i++) { + owners[i] = ioif; + } + local_irq_restore(flags); + + notify_watchers(); + return 0; +} + + +/* port can be 'a', 'b' or 'g' */ +int cris_io_interface_free_pins(const enum cris_io_interface ioif, + const char port, + const unsigned start_bit, + const unsigned stop_bit) +{ + unsigned int i; + unsigned int mask = 0; + unsigned int tmp_mask; + unsigned long int flags; + enum cris_io_interface *owners; + + (void)cris_io_interface_init(); + + if (!((start_bit <= stop_bit) && + ((((port == 'a') || (port == 'b')) && (stop_bit < 8)) || + ((port == 'g') && (stop_bit < 32))))) { + return -EINVAL; + } + + mask = create_mask(stop_bit + 1); + tmp_mask = create_mask(start_bit); + mask &= ~tmp_mask; + + DBG(printk("cris_io_interface_free_pins: port=%c start=%u stop=%u mask=0x%08x\n", + port, start_bit, stop_bit, mask)); + + local_irq_save(flags); + + switch (port) { + case 'a': + if ((~gpio_pa_pins & mask) != mask) { + local_irq_restore(flags); + printk(KERN_CRIT "cris_io_interface_free_pins: Freeing free pins"); + } + owners = gpio_pa_owners; + break; + case 'b': + if ((~gpio_pb_pins & mask) != mask) { + local_irq_restore(flags); + printk(KERN_CRIT "cris_io_interface_free_pins: Freeing free pins"); + } + owners = gpio_pb_owners; + break; + case 'g': + if (((~gpio_in_pins & mask) != mask) || + ((~gpio_out_pins & mask) != mask)) { + local_irq_restore(flags); + printk(KERN_CRIT "cris_io_interface_free_pins: Freeing free pins"); + } + owners = gpio_pg_owners; + break; + default: + owners = NULL; /* Cannot happen. Shut up, gcc! */ + } + + for (i = start_bit; i <= stop_bit; i++) { + if (owners[i] != ioif) { + printk(KERN_CRIT "cris_io_interface_free_pins: Freeing unowned pins"); + } + } + + /* All was ok, change data. */ + switch (port) { + case 'a': + gpio_pa_pins |= mask; + break; + case 'b': + gpio_pb_pins |= mask; + break; + case 'g': + gpio_in_pins |= mask; + gpio_out_pins |= mask; + break; + } + + for (i = start_bit; i <= stop_bit; i++) { + owners[i] = if_unclaimed; + } + local_irq_restore(flags); + notify_watchers(); + + return 0; +} + + +int cris_io_interface_register_watcher(void (*notify)(const unsigned int gpio_in_available, + const unsigned int gpio_out_available, + const unsigned char pa_available, + const unsigned char pb_available)) +{ + struct watcher *w; + + (void)cris_io_interface_init(); + + if (NULL == notify) { + return -EINVAL; + } + w = kmalloc(sizeof(*w), GFP_KERNEL); + if (!w) { + return -ENOMEM; + } + w->notify = notify; + w->next = watchers; + watchers = w; + + w->notify((const unsigned int)gpio_in_pins, + (const unsigned int)gpio_out_pins, + (const unsigned char)gpio_pa_pins, + (const unsigned char)gpio_pb_pins); + + return 0; +} + +void cris_io_interface_delete_watcher(void (*notify)(const unsigned int gpio_in_available, + const unsigned int gpio_out_available, + const unsigned char pa_available, + const unsigned char pb_available)) +{ + struct watcher *w = watchers, *prev = NULL; + + (void)cris_io_interface_init(); + + while ((NULL != w) && (w->notify != notify)){ + prev = w; + w = w->next; + } + if (NULL != w) { + if (NULL != prev) { + prev->next = w->next; + } else { + watchers = w->next; + } + kfree(w); + return; + } + printk(KERN_WARNING "cris_io_interface_delete_watcher: Deleting unknown watcher 0x%p\n", notify); +} + + +static int cris_io_interface_init(void) +{ + static int first = 1; + int i; + + if (!first) { + return 0; + } + first = 0; + + for (i = 0; i<8; i++) { + gpio_pa_owners[i] = if_unclaimed; + gpio_pb_owners[i] = if_unclaimed; + gpio_pg_owners[i] = if_unclaimed; + } + for (; i<32; i++) { + gpio_pg_owners[i] = if_unclaimed; + } + return 0; +} + + +module_init(cris_io_interface_init); + + +EXPORT_SYMBOL(cris_request_io_interface); +EXPORT_SYMBOL(cris_free_io_interface); +EXPORT_SYMBOL(cris_io_interface_allocate_pins); +EXPORT_SYMBOL(cris_io_interface_free_pins); +EXPORT_SYMBOL(cris_io_interface_register_watcher); +EXPORT_SYMBOL(cris_io_interface_delete_watcher); diff --git a/arch/cris/arch-v10/kernel/irq.c b/arch/cris/arch-v10/kernel/irq.c new file mode 100644 index 000000000..09cae80a8 --- /dev/null +++ b/arch/cris/arch-v10/kernel/irq.c @@ -0,0 +1,235 @@ +/* + * linux/arch/cris/kernel/irq.c + * + * Copyright (c) 2000-2002 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + * This file contains the interrupt vectors and some + * helper functions + * + */ + +#include <asm/irq.h> +#include <asm/current.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/init.h> + +#define crisv10_mask_irq(irq_nr) (*R_VECT_MASK_CLR = 1 << (irq_nr)); +#define crisv10_unmask_irq(irq_nr) (*R_VECT_MASK_SET = 1 << (irq_nr)); + +extern void kgdb_init(void); +extern void breakpoint(void); + +/* don't use set_int_vector, it bypasses the linux interrupt handlers. it is + * global just so that the kernel gdb can use it. + */ + +void +set_int_vector(int n, irqvectptr addr) +{ + etrax_irv->v[n + 0x20] = (irqvectptr)addr; +} + +/* the breakpoint vector is obviously not made just like the normal irq handlers + * but needs to contain _code_ to jump to addr. + * + * the BREAK n instruction jumps to IBR + n * 8 + */ + +void +set_break_vector(int n, irqvectptr addr) +{ + unsigned short *jinstr = (unsigned short *)&etrax_irv->v[n*2]; + unsigned long *jaddr = (unsigned long *)(jinstr + 1); + + /* if you don't know what this does, do not touch it! */ + + *jinstr = 0x0d3f; + *jaddr = (unsigned long)addr; + + /* 00000026 <clrlop+1a> 3f0d82000000 jump 0x82 */ +} + +/* + * This builds up the IRQ handler stubs using some ugly macros in irq.h + * + * These macros create the low-level assembly IRQ routines that do all + * the operations that are needed. They are also written to be fast - and to + * disable interrupts as little as humanly possible. + * + */ + +/* IRQ0 and 1 are special traps */ +void hwbreakpoint(void); +void IRQ1_interrupt(void); +BUILD_TIMER_IRQ(2, 0x04) /* the timer interrupt is somewhat special */ +BUILD_IRQ(3, 0x08) +BUILD_IRQ(4, 0x10) +BUILD_IRQ(5, 0x20) +BUILD_IRQ(6, 0x40) +BUILD_IRQ(7, 0x80) +BUILD_IRQ(8, 0x100) +BUILD_IRQ(9, 0x200) +BUILD_IRQ(10, 0x400) +BUILD_IRQ(11, 0x800) +BUILD_IRQ(12, 0x1000) +BUILD_IRQ(13, 0x2000) +void mmu_bus_fault(void); /* IRQ 14 is the bus fault interrupt */ +void multiple_interrupt(void); /* IRQ 15 is the multiple IRQ interrupt */ +BUILD_IRQ(16, 0x10000 | 0x20000) /* ethernet tx interrupt needs to block rx */ +BUILD_IRQ(17, 0x20000 | 0x10000) /* ...and vice versa */ +BUILD_IRQ(18, 0x40000) +BUILD_IRQ(19, 0x80000) +BUILD_IRQ(20, 0x100000) +BUILD_IRQ(21, 0x200000) +BUILD_IRQ(22, 0x400000) +BUILD_IRQ(23, 0x800000) +BUILD_IRQ(24, 0x1000000) +BUILD_IRQ(25, 0x2000000) +/* IRQ 26-30 are reserved */ +BUILD_IRQ(31, 0x80000000) + +/* + * Pointers to the low-level handlers + */ + +static void (*interrupt[NR_IRQS])(void) = { + NULL, NULL, IRQ2_interrupt, IRQ3_interrupt, + IRQ4_interrupt, IRQ5_interrupt, IRQ6_interrupt, IRQ7_interrupt, + IRQ8_interrupt, IRQ9_interrupt, IRQ10_interrupt, IRQ11_interrupt, + IRQ12_interrupt, IRQ13_interrupt, NULL, NULL, + IRQ16_interrupt, IRQ17_interrupt, IRQ18_interrupt, IRQ19_interrupt, + IRQ20_interrupt, IRQ21_interrupt, IRQ22_interrupt, IRQ23_interrupt, + IRQ24_interrupt, IRQ25_interrupt, NULL, NULL, NULL, NULL, NULL, + IRQ31_interrupt +}; + +static void enable_crisv10_irq(struct irq_data *data) +{ + crisv10_unmask_irq(data->irq); +} + +static void disable_crisv10_irq(struct irq_data *data) +{ + crisv10_mask_irq(data->irq); +} + +static struct irq_chip crisv10_irq_type = { + .name = "CRISv10", + .irq_shutdown = disable_crisv10_irq, + .irq_enable = enable_crisv10_irq, + .irq_disable = disable_crisv10_irq, +}; + +void weird_irq(void); +void system_call(void); /* from entry.S */ +void do_sigtrap(void); /* from entry.S */ +void gdb_handle_breakpoint(void); /* from entry.S */ + +extern void do_IRQ(int irq, struct pt_regs * regs); + +/* Handle multiple IRQs */ +void do_multiple_IRQ(struct pt_regs* regs) +{ + int bit; + unsigned masked; + unsigned mask; + unsigned ethmask = 0; + + /* Get interrupts to mask and handle */ + mask = masked = *R_VECT_MASK_RD; + + /* Never mask timer IRQ */ + mask &= ~(IO_MASK(R_VECT_MASK_RD, timer0)); + + /* + * If either ethernet interrupt (rx or tx) is active then block + * the other one too. Unblock afterwards also. + */ + if (mask & + (IO_STATE(R_VECT_MASK_RD, dma0, active) | + IO_STATE(R_VECT_MASK_RD, dma1, active))) { + ethmask = (IO_MASK(R_VECT_MASK_RD, dma0) | + IO_MASK(R_VECT_MASK_RD, dma1)); + } + + /* Block them */ + *R_VECT_MASK_CLR = (mask | ethmask); + + /* An extra irq_enter here to prevent softIRQs to run after + * each do_IRQ. This will decrease the interrupt latency. + */ + irq_enter(); + + /* Handle all IRQs */ + for (bit = 2; bit < 32; bit++) { + if (masked & (1 << bit)) { + do_IRQ(bit, regs); + } + } + + /* This irq_exit() will trigger the soft IRQs. */ + irq_exit(); + + /* Unblock the IRQs again */ + *R_VECT_MASK_SET = (masked | ethmask); +} + +/* init_IRQ() is called by start_kernel and is responsible for fixing IRQ masks and + setting the irq vector table. +*/ + +void __init init_IRQ(void) +{ + int i; + + /* clear all interrupt masks */ + *R_IRQ_MASK0_CLR = 0xffffffff; + *R_IRQ_MASK1_CLR = 0xffffffff; + *R_IRQ_MASK2_CLR = 0xffffffff; + *R_VECT_MASK_CLR = 0xffffffff; + + for (i = 0; i < 256; i++) + etrax_irv->v[i] = weird_irq; + + /* Initialize IRQ handler descriptors. */ + for(i = 2; i < NR_IRQS; i++) { + irq_set_chip_and_handler(i, &crisv10_irq_type, + handle_simple_irq); + set_int_vector(i, interrupt[i]); + } + + /* the entries in the break vector contain actual code to be + executed by the associated break handler, rather than just a jump + address. therefore we need to setup a default breakpoint handler + for all breakpoints */ + for (i = 0; i < 16; i++) + set_break_vector(i, do_sigtrap); + + /* except IRQ 15 which is the multiple-IRQ handler on Etrax100 */ + set_int_vector(15, multiple_interrupt); + + /* 0 and 1 which are special breakpoint/NMI traps */ + set_int_vector(0, hwbreakpoint); + set_int_vector(1, IRQ1_interrupt); + + /* and irq 14 which is the mmu bus fault handler */ + set_int_vector(14, mmu_bus_fault); + + /* setup the system-call trap, which is reached by BREAK 13 */ + set_break_vector(13, system_call); + + /* setup a breakpoint handler for debugging used for both user and + kernel mode debugging (which is why it is not inside an ifdef + CONFIG_ETRAX_KGDB) */ + set_break_vector(8, gdb_handle_breakpoint); + +#ifdef CONFIG_ETRAX_KGDB + /* setup kgdb if its enabled, and break into the debugger */ + kgdb_init(); + breakpoint(); +#endif +} diff --git a/arch/cris/arch-v10/kernel/kgdb.c b/arch/cris/arch-v10/kernel/kgdb.c new file mode 100644 index 000000000..22d846bfc --- /dev/null +++ b/arch/cris/arch-v10/kernel/kgdb.c @@ -0,0 +1,1144 @@ +/*!************************************************************************** +*! +*! FILE NAME : kgdb.c +*! +*! DESCRIPTION: Implementation of the gdb stub with respect to ETRAX 100. +*! It is a mix of arch/m68k/kernel/kgdb.c and cris_stub.c. +*! +*!--------------------------------------------------------------------------- +*! HISTORY +*! +*! DATE NAME CHANGES +*! ---- ---- ------- +*! Apr 26 1999 Hendrik Ruijter Initial version. +*! May 6 1999 Hendrik Ruijter Removed call to strlen in libc and removed +*! struct assignment as it generates calls to +*! memcpy in libc. +*! Jun 17 1999 Hendrik Ruijter Added gdb 4.18 support. 'X', 'qC' and 'qL'. +*! Jul 21 1999 Bjorn Wesen eLinux port +*! +*!--------------------------------------------------------------------------- +*! +*! (C) Copyright 1999, Axis Communications AB, LUND, SWEDEN +*! +*!**************************************************************************/ +/* @(#) cris_stub.c 1.3 06/17/99 */ + +/* + * kgdb usage notes: + * ----------------- + * + * If you select CONFIG_ETRAX_KGDB in the configuration, the kernel will be + * built with different gcc flags: "-g" is added to get debug infos, and + * "-fomit-frame-pointer" is omitted to make debugging easier. Since the + * resulting kernel will be quite big (approx. > 7 MB), it will be stripped + * before compresion. Such a kernel will behave just as usually, except if + * given a "debug=<device>" command line option. (Only serial devices are + * allowed for <device>, i.e. no printers or the like; possible values are + * machine depedend and are the same as for the usual debug device, the one + * for logging kernel messages.) If that option is given and the device can be + * initialized, the kernel will connect to the remote gdb in trap_init(). The + * serial parameters are fixed to 8N1 and 115200 bps, for easyness of + * implementation. + * + * To start a debugging session, start that gdb with the debugging kernel + * image (the one with the symbols, vmlinux.debug) named on the command line. + * This file will be used by gdb to get symbol and debugging infos about the + * kernel. Next, select remote debug mode by + * target remote <device> + * where <device> is the name of the serial device over which the debugged + * machine is connected. Maybe you have to adjust the baud rate by + * set remotebaud <rate> + * or also other parameters with stty: + * shell stty ... </dev/... + * If the kernel to debug has already booted, it waited for gdb and now + * connects, and you'll see a breakpoint being reported. If the kernel isn't + * running yet, start it now. The order of gdb and the kernel doesn't matter. + * Another thing worth knowing about in the getting-started phase is how to + * debug the remote protocol itself. This is activated with + * set remotedebug 1 + * gdb will then print out each packet sent or received. You'll also get some + * messages about the gdb stub on the console of the debugged machine. + * + * If all that works, you can use lots of the usual debugging techniques on + * the kernel, e.g. inspecting and changing variables/memory, setting + * breakpoints, single stepping and so on. It's also possible to interrupt the + * debugged kernel by pressing C-c in gdb. Have fun! :-) + * + * The gdb stub is entered (and thus the remote gdb gets control) in the + * following situations: + * + * - If breakpoint() is called. This is just after kgdb initialization, or if + * a breakpoint() call has been put somewhere into the kernel source. + * (Breakpoints can of course also be set the usual way in gdb.) + * In eLinux, we call breakpoint() in init/main.c after IRQ initialization. + * + * - If there is a kernel exception, i.e. bad_super_trap() or die_if_kernel() + * are entered. All the CPU exceptions are mapped to (more or less..., see + * the hard_trap_info array below) appropriate signal, which are reported + * to gdb. die_if_kernel() is usually called after some kind of access + * error and thus is reported as SIGSEGV. + * + * - When panic() is called. This is reported as SIGABRT. + * + * - If C-c is received over the serial line, which is treated as + * SIGINT. + * + * Of course, all these signals are just faked for gdb, since there is no + * signal concept as such for the kernel. It also isn't possible --obviously-- + * to set signal handlers from inside gdb, or restart the kernel with a + * signal. + * + * Current limitations: + * + * - While the kernel is stopped, interrupts are disabled for safety reasons + * (i.e., variables not changing magically or the like). But this also + * means that the clock isn't running anymore, and that interrupts from the + * hardware may get lost/not be served in time. This can cause some device + * errors... + * + * - When single-stepping, only one instruction of the current thread is + * executed, but interrupts are allowed for that time and will be serviced + * if pending. Be prepared for that. + * + * - All debugging happens in kernel virtual address space. There's no way to + * access physical memory not mapped in kernel space, or to access user + * space. A way to work around this is using get_user_long & Co. in gdb + * expressions, but only for the current process. + * + * - Interrupting the kernel only works if interrupts are currently allowed, + * and the interrupt of the serial line isn't blocked by some other means + * (IPL too high, disabled, ...) + * + * - The gdb stub is currently not reentrant, i.e. errors that happen therein + * (e.g. accessing invalid memory) may not be caught correctly. This could + * be removed in future by introducing a stack of struct registers. + * + */ + +/* + * To enable debugger support, two things need to happen. One, a + * call to kgdb_init() is necessary in order to allow any breakpoints + * or error conditions to be properly intercepted and reported to gdb. + * Two, a breakpoint needs to be generated to begin communication. This + * is most easily accomplished by a call to breakpoint(). + * + * 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 + * + * c Resume at current address SNN ( signal NN) + * cAA..AA Continue at address AA..AA SNN + * + * s Step one instruction SNN + * sAA..AA Step one instruction from AA..AA SNN + * + * k kill + * + * ? What was the last sigval ? SNN (signal NN) + * + * bBB..BB Set baud rate to BB..BB OK or BNN, then sets + * baud rate + * + * All commands and responses are sent with a packet which includes a + * checksum. A packet consists of + * + * $<packet info>#<checksum>. + * + * where + * <packet info> :: <characters representing the command or response> + * <checksum> :: < two hex digits computed as modulo 256 sum of <packetinfo>> + * + * When a packet is received, it is first acknowledged with either '+' or '-'. + * '+' indicates a successful transfer. '-' indicates a failed transfer. + * + * Example: + * + * Host: Reply: + * $m0,10#2a +$00010203040506070809101112131415#42 + * + */ + + +#include <linux/string.h> +#include <linux/signal.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/linkage.h> +#include <linux/reboot.h> + +#include <asm/setup.h> +#include <asm/ptrace.h> + +#include <arch/svinto.h> +#include <asm/irq.h> + +static int kgdb_started = 0; + +/********************************* Register image ****************************/ +/* Use the order of registers as defined in "AXIS ETRAX CRIS Programmer's + Reference", p. 1-1, with the additional register definitions of the + ETRAX 100LX in cris-opc.h. + There are 16 general 32-bit registers, R0-R15, where R14 is the stack + pointer, SP, and R15 is the program counter, PC. + There are 16 special registers, P0-P15, where three of the unimplemented + registers, P0, P4 and P8, are reserved as zero-registers. A read from + any of these registers returns zero and a write has no effect. */ + +typedef +struct register_image +{ + /* Offset */ + unsigned int r0; /* 0x00 */ + unsigned int r1; /* 0x04 */ + unsigned int r2; /* 0x08 */ + unsigned int r3; /* 0x0C */ + unsigned int r4; /* 0x10 */ + unsigned int r5; /* 0x14 */ + unsigned int r6; /* 0x18 */ + unsigned int r7; /* 0x1C */ + unsigned int r8; /* 0x20 Frame pointer */ + unsigned int r9; /* 0x24 */ + unsigned int r10; /* 0x28 */ + unsigned int r11; /* 0x2C */ + unsigned int r12; /* 0x30 */ + unsigned int r13; /* 0x34 */ + unsigned int sp; /* 0x38 Stack pointer */ + unsigned int pc; /* 0x3C Program counter */ + + unsigned char p0; /* 0x40 8-bit zero-register */ + unsigned char vr; /* 0x41 Version register */ + + unsigned short p4; /* 0x42 16-bit zero-register */ + unsigned short ccr; /* 0x44 Condition code register */ + + unsigned int mof; /* 0x46 Multiply overflow register */ + + unsigned int p8; /* 0x4A 32-bit zero-register */ + unsigned int ibr; /* 0x4E Interrupt base register */ + unsigned int irp; /* 0x52 Interrupt return pointer */ + unsigned int srp; /* 0x56 Subroutine return pointer */ + unsigned int bar; /* 0x5A Breakpoint address register */ + unsigned int dccr; /* 0x5E Double condition code register */ + unsigned int brp; /* 0x62 Breakpoint return pointer (pc in caller) */ + unsigned int usp; /* 0x66 User mode stack pointer */ +} registers; + +/* Serial port, reads one character. ETRAX 100 specific. from debugport.c */ +int getDebugChar (void); + +/* Serial port, writes one character. ETRAX 100 specific. from debugport.c */ +void putDebugChar (int val); + +void enableDebugIRQ (void); + +/******************** Prototypes for global functions. ***********************/ + +/* The string str is prepended with the GDB printout token and sent. */ +void putDebugString (const unsigned char *str, int length); /* used by etrax100ser.c */ + +/* The hook for both static (compiled) and dynamic breakpoints set by GDB. + ETRAX 100 specific. */ +void handle_breakpoint (void); /* used by irq.c */ + +/* The hook for an interrupt generated by GDB. ETRAX 100 specific. */ +void handle_interrupt (void); /* used by irq.c */ + +/* A static breakpoint to be used at startup. */ +void breakpoint (void); /* called by init/main.c */ + +/* From osys_int.c, executing_task contains the number of the current + executing task in osys. Does not know of object-oriented threads. */ +extern unsigned char executing_task; + +/* The number of characters used for a 64 bit thread identifier. */ +#define HEXCHARS_IN_THREAD_ID 16 + +/********************************** Packet I/O ******************************/ +/* BUFMAX defines the maximum number of characters in + inbound/outbound buffers */ +#define BUFMAX 512 + +/* Run-length encoding maximum length. Send 64 at most. */ +#define RUNLENMAX 64 + +/* The inbound/outbound buffers used in packet I/O */ +static char remcomInBuffer[BUFMAX]; +static char remcomOutBuffer[BUFMAX]; + +/* Error and warning messages. */ +enum error_type +{ + SUCCESS, E01, E02, E03, E04, E05, E06, E07 +}; +static char *error_message[] = +{ + "", + "E01 Set current or general thread - H[c,g] - internal error.", + "E02 Change register content - P - cannot change read-only register.", + "E03 Thread is not alive.", /* T, not used. */ + "E04 The command is not supported - [s,C,S,!,R,d,r] - internal error.", + "E05 Change register content - P - the register is not implemented..", + "E06 Change memory content - M - internal error.", + "E07 Change register content - P - the register is not stored on the stack" +}; +/********************************* Register image ****************************/ +/* Use the order of registers as defined in "AXIS ETRAX CRIS Programmer's + Reference", p. 1-1, with the additional register definitions of the + ETRAX 100LX in cris-opc.h. + There are 16 general 32-bit registers, R0-R15, where R14 is the stack + pointer, SP, and R15 is the program counter, PC. + There are 16 special registers, P0-P15, where three of the unimplemented + registers, P0, P4 and P8, are reserved as zero-registers. A read from + any of these registers returns zero and a write has no effect. */ +enum register_name +{ + R0, R1, R2, R3, + R4, R5, R6, R7, + R8, R9, R10, R11, + R12, R13, SP, PC, + P0, VR, P2, P3, + P4, CCR, P6, MOF, + P8, IBR, IRP, SRP, + BAR, DCCR, BRP, USP +}; + +/* The register sizes of the registers in register_name. An unimplemented register + is designated by size 0 in this array. */ +static int register_size[] = +{ + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 1, 1, 0, 0, + 2, 2, 0, 4, + 4, 4, 4, 4, + 4, 4, 4, 4 +}; + +/* Contains the register image of the executing thread in the assembler + part of the code in order to avoid horrible addressing modes. */ +registers cris_reg; + +/* FIXME: Should this be used? Delete otherwise. */ +/* Contains the assumed consistency state of the register image. Uses the + enum error_type for state information. */ +static int consistency_status = SUCCESS; + +/********************************** Handle exceptions ************************/ +/* The variable cris_reg contains the register image associated with the + current_thread_c variable. It is a complete register image created at + entry. The reg_g contains a register image of a task where the general + registers are taken from the stack and all special registers are taken + from the executing task. It is associated with current_thread_g and used + in order to provide access mainly for 'g', 'G' and 'P'. +*/ + +/********************************** Breakpoint *******************************/ +/* Use an internal stack in the breakpoint and interrupt response routines */ +#define INTERNAL_STACK_SIZE 1024 +char internal_stack[INTERNAL_STACK_SIZE]; + +/* Due to the breakpoint return pointer, a state variable is needed to keep + track of whether it is a static (compiled) or dynamic (gdb-invoked) + breakpoint to be handled. A static breakpoint uses the content of register + BRP as it is whereas a dynamic breakpoint requires subtraction with 2 + in order to execute the instruction. The first breakpoint is static. */ +static unsigned char is_dyn_brkp = 0; + +/********************************* String library ****************************/ +/* Single-step over library functions creates trap loops. */ + +/* Copy char s2[] to s1[]. */ +static char* +gdb_cris_strcpy (char *s1, const char *s2) +{ + char *s = s1; + + for (s = s1; (*s++ = *s2++) != '\0'; ) + ; + return (s1); +} + +/* Find length of s[]. */ +static int +gdb_cris_strlen (const char *s) +{ + const char *sc; + + for (sc = s; *sc != '\0'; sc++) + ; + return (sc - s); +} + +/* Find first occurrence of c in s[n]. */ +static void* +gdb_cris_memchr (const void *s, int c, int n) +{ + const unsigned char uc = c; + const unsigned char *su; + + for (su = s; 0 < n; ++su, --n) + if (*su == uc) + return ((void *)su); + return (NULL); +} +/******************************* Standard library ****************************/ +/* Single-step over library functions creates trap loops. */ +/* Convert string to long. */ +static int +gdb_cris_strtol (const char *s, char **endptr, int base) +{ + char *s1; + char *sd; + int x = 0; + + for (s1 = (char*)s; (sd = gdb_cris_memchr(hex_asc, *s1, base)) != NULL; ++s1) + x = x * base + (sd - hex_asc); + + if (endptr) + { + /* Unconverted suffix is stored in endptr unless endptr is NULL. */ + *endptr = s1; + } + + return x; +} + +/********************************** Packet I/O ******************************/ +/* Returns the integer equivalent of a hexadecimal character. */ +static int +hex (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 hexadecimal representation. + Put the result in buf, and return a pointer to the last character + in buf (null). */ + +static char * +mem2hex(char *buf, unsigned char *mem, int count) +{ + int i; + int ch; + + if (mem == NULL) { + /* Bogus read from m0. FIXME: What constitutes a valid address? */ + for (i = 0; i < count; i++) { + *buf++ = '0'; + *buf++ = '0'; + } + } else { + /* Valid mem address. */ + for (i = 0; i < count; i++) { + ch = *mem++; + buf = hex_byte_pack(buf, ch); + } + } + + /* Terminate properly. */ + *buf = '\0'; + return (buf); +} + +/* Convert the array, in hexadecimal representation, pointed to by buf into + binary representation. Put the result in mem, and return a pointer to + the character after the last byte written. */ +static unsigned char* +hex2mem (unsigned char *mem, char *buf, 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); +} + +/* Put the content of the array, in binary representation, pointed to by buf + into memory pointed to by mem, and return a pointer to the character after + the last byte written. + Gdb will escape $, #, and the escape char (0x7d). */ +static unsigned char* +bin2mem (unsigned char *mem, unsigned char *buf, int count) +{ + int i; + unsigned char *next; + for (i = 0; i < count; i++) { + /* Check for any escaped characters. Be paranoid and + only unescape chars that should be escaped. */ + if (*buf == 0x7d) { + next = buf + 1; + if (*next == 0x3 || *next == 0x4 || *next == 0x5D) /* #, $, ESC */ + { + buf++; + *buf += 0x20; + } + } + *mem++ = *buf++; + } + return (mem); +} + +/* Await the sequence $<data>#<checksum> and store <data> in the array buffer + returned. */ +static void +getpacket (char *buffer) +{ + unsigned char checksum; + unsigned char xmitcsum; + int i; + int count; + char ch; + do { + while ((ch = getDebugChar ()) != '$') + /* Wait for the start character $ and ignore all other characters */; + checksum = 0; + xmitcsum = -1; + count = 0; + /* Read until a # or the end of the buffer is reached */ + while (count < BUFMAX - 1) { + ch = getDebugChar (); + if (ch == '#') + break; + checksum = checksum + ch; + buffer[count] = ch; + count = count + 1; + } + buffer[count] = '\0'; + + if (ch == '#') { + xmitcsum = hex (getDebugChar ()) << 4; + xmitcsum += hex (getDebugChar ()); + if (checksum != xmitcsum) { + /* Wrong checksum */ + putDebugChar ('-'); + } + else { + /* Correct checksum */ + putDebugChar ('+'); + /* If sequence characters are received, reply with them */ + if (buffer[2] == ':') { + putDebugChar (buffer[0]); + putDebugChar (buffer[1]); + /* Remove the sequence characters from the buffer */ + count = gdb_cris_strlen (buffer); + for (i = 3; i <= count; i++) + buffer[i - 3] = buffer[i]; + } + } + } + } while (checksum != xmitcsum); +} + +/* Send $<data>#<checksum> from the <data> in the array buffer. */ + +static void +putpacket(char *buffer) +{ + int checksum; + int runlen; + int encode; + + do { + char *src = buffer; + putDebugChar ('$'); + checksum = 0; + while (*src) { + /* Do run length encoding */ + putDebugChar (*src); + checksum += *src; + runlen = 0; + while (runlen < RUNLENMAX && *src == src[runlen]) { + runlen++; + } + if (runlen > 3) { + /* Got a useful amount */ + putDebugChar ('*'); + checksum += '*'; + encode = runlen + ' ' - 4; + putDebugChar (encode); + checksum += encode; + src += runlen; + } + else { + src++; + } + } + putDebugChar('#'); + putDebugChar(hex_asc_hi(checksum)); + putDebugChar(hex_asc_lo(checksum)); + } while(kgdb_started && (getDebugChar() != '+')); +} + +/* The string str is prepended with the GDB printout token and sent. Required + in traditional implementations. */ +void +putDebugString (const unsigned char *str, int length) +{ + remcomOutBuffer[0] = 'O'; + mem2hex(&remcomOutBuffer[1], (unsigned char *)str, length); + putpacket(remcomOutBuffer); +} + +/********************************* Register image ****************************/ +/* Write a value to a specified register in the register image of the current + thread. Returns status code SUCCESS, E02 or E05. */ +static int +write_register (int regno, char *val) +{ + int status = SUCCESS; + registers *current_reg = &cris_reg; + + if (regno >= R0 && regno <= PC) { + /* 32-bit register with simple offset. */ + hex2mem ((unsigned char *)current_reg + regno * sizeof(unsigned int), + val, sizeof(unsigned int)); + } + else if (regno == P0 || regno == VR || regno == P4 || regno == P8) { + /* Do not support read-only registers. */ + status = E02; + } + else if (regno == CCR) { + /* 16 bit register with complex offset. (P4 is read-only, P6 is not implemented, + and P7 (MOF) is 32 bits in ETRAX 100LX. */ + hex2mem ((unsigned char *)&(current_reg->ccr) + (regno-CCR) * sizeof(unsigned short), + val, sizeof(unsigned short)); + } + else if (regno >= MOF && regno <= USP) { + /* 32 bit register with complex offset. (P8 has been taken care of.) */ + hex2mem ((unsigned char *)&(current_reg->ibr) + (regno-IBR) * sizeof(unsigned int), + val, sizeof(unsigned int)); + } + else { + /* Do not support nonexisting or unimplemented registers (P2, P3, and P6). */ + status = E05; + } + return status; +} + +/* Read a value from a specified register in the register image. Returns the + value in the register or -1 for non-implemented registers. + Should check consistency_status after a call which may be E05 after changes + in the implementation. */ +static int +read_register (char regno, unsigned int *valptr) +{ + registers *current_reg = &cris_reg; + + if (regno >= R0 && regno <= PC) { + /* 32-bit register with simple offset. */ + *valptr = *(unsigned int *)((char *)current_reg + regno * sizeof(unsigned int)); + return SUCCESS; + } + else if (regno == P0 || regno == VR) { + /* 8 bit register with complex offset. */ + *valptr = (unsigned int)(*(unsigned char *) + ((char *)&(current_reg->p0) + (regno-P0) * sizeof(char))); + return SUCCESS; + } + else if (regno == P4 || regno == CCR) { + /* 16 bit register with complex offset. */ + *valptr = (unsigned int)(*(unsigned short *) + ((char *)&(current_reg->p4) + (regno-P4) * sizeof(unsigned short))); + return SUCCESS; + } + else if (regno >= MOF && regno <= USP) { + /* 32 bit register with complex offset. */ + *valptr = *(unsigned int *)((char *)&(current_reg->p8) + + (regno-P8) * sizeof(unsigned int)); + return SUCCESS; + } + else { + /* Do not support nonexisting or unimplemented registers (P2, P3, and P6). */ + consistency_status = E05; + return E05; + } +} + +/********************************** Handle exceptions ************************/ +/* Build and send a response packet in order to inform the host the + stub is stopped. TAAn...:r...;n...:r...;n...:r...; + AA = signal number + n... = register number (hex) + r... = register contents + n... = `thread' + r... = thread process ID. This is a hex integer. + n... = other string not starting with valid hex digit. + gdb should ignore this n,r pair and go on to the next. + This way we can extend the protocol. */ +static void +stub_is_stopped(int sigval) +{ + char *ptr = remcomOutBuffer; + int regno; + + unsigned int reg_cont; + int status; + + /* Send trap type (converted to signal) */ + + *ptr++ = 'T'; + ptr = hex_byte_pack(ptr, sigval); + + /* Send register contents. We probably only need to send the + * PC, frame pointer and stack pointer here. Other registers will be + * explicitly asked for. But for now, send all. + */ + + for (regno = R0; regno <= USP; regno++) { + /* Store n...:r...; for the registers in the buffer. */ + + status = read_register (regno, ®_cont); + + if (status == SUCCESS) { + ptr = hex_byte_pack(ptr, regno); + *ptr++ = ':'; + + ptr = mem2hex(ptr, (unsigned char *)®_cont, + register_size[regno]); + *ptr++ = ';'; + } + + } + + /* null-terminate and send it off */ + + *ptr = 0; + + putpacket (remcomOutBuffer); +} + +/* Performs a complete re-start from scratch. */ +static void +kill_restart (void) +{ + machine_restart(""); +} + +/* All expected commands are sent from remote.c. Send a response according + to the description in remote.c. */ +void +handle_exception (int sigval) +{ + /* Send response. */ + + stub_is_stopped (sigval); + + for (;;) { + remcomOutBuffer[0] = '\0'; + getpacket (remcomInBuffer); + switch (remcomInBuffer[0]) { + case 'g': + /* Read registers: g + Success: Each byte of register data is described by two hex digits. + Registers are in the internal order for GDB, and the bytes + in a register are in the same order the machine uses. + Failure: void. */ + + mem2hex(remcomOutBuffer, (char *)&cris_reg, sizeof(registers)); + break; + + case 'G': + /* Write registers. GXX..XX + Each byte of register data is described by two hex digits. + Success: OK + Failure: void. */ + hex2mem((char *)&cris_reg, &remcomInBuffer[1], sizeof(registers)); + gdb_cris_strcpy (remcomOutBuffer, "OK"); + break; + + case 'P': + /* Write register. Pn...=r... + Write register n..., hex value without 0x, with value r..., + which contains a hex value without 0x and two hex digits + for each byte in the register (target byte order). P1f=11223344 means + set register 31 to 44332211. + Success: OK + Failure: E02, E05 */ + { + char *suffix; + int regno = gdb_cris_strtol (&remcomInBuffer[1], &suffix, 16); + int status; + status = write_register (regno, suffix+1); + + switch (status) { + case E02: + /* Do not support read-only registers. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E02]); + break; + case E05: + /* Do not support non-existing registers. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E05]); + break; + case E07: + /* Do not support non-existing registers on the stack. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E07]); + break; + default: + /* Valid register number. */ + gdb_cris_strcpy (remcomOutBuffer, "OK"); + break; + } + } + break; + + case 'm': + /* Read from memory. mAA..AA,LLLL + AA..AA is the address and LLLL is the length. + Success: XX..XX is the memory content. Can be fewer bytes than + requested if only part of the data may be read. m6000120a,6c means + retrieve 108 byte from base address 6000120a. + Failure: void. */ + { + char *suffix; + unsigned char *addr = (unsigned char *)gdb_cris_strtol(&remcomInBuffer[1], + &suffix, 16); int length = gdb_cris_strtol(suffix+1, 0, 16); + + mem2hex(remcomOutBuffer, addr, length); + } + break; + + case 'X': + /* Write to memory. XAA..AA,LLLL:XX..XX + AA..AA is the start address, LLLL is the number of bytes, and + XX..XX is the binary data. + Success: OK + Failure: void. */ + case 'M': + /* Write to memory. MAA..AA,LLLL:XX..XX + AA..AA is the start address, LLLL is the number of bytes, and + XX..XX is the hexadecimal data. + Success: OK + Failure: void. */ + { + char *lenptr; + char *dataptr; + unsigned char *addr = (unsigned char *)gdb_cris_strtol(&remcomInBuffer[1], + &lenptr, 16); + int length = gdb_cris_strtol(lenptr+1, &dataptr, 16); + if (*lenptr == ',' && *dataptr == ':') { + if (remcomInBuffer[0] == 'M') { + hex2mem(addr, dataptr + 1, length); + } + else /* X */ { + bin2mem(addr, dataptr + 1, length); + } + gdb_cris_strcpy (remcomOutBuffer, "OK"); + } + else { + gdb_cris_strcpy (remcomOutBuffer, error_message[E06]); + } + } + break; + + case 'c': + /* Continue execution. cAA..AA + AA..AA is the address where execution is resumed. If AA..AA is + omitted, resume at the present address. + Success: return to the executing thread. + Failure: will never know. */ + if (remcomInBuffer[1] != '\0') { + cris_reg.pc = gdb_cris_strtol (&remcomInBuffer[1], 0, 16); + } + enableDebugIRQ(); + return; + + case 's': + /* Step. sAA..AA + AA..AA is the address where execution is resumed. If AA..AA is + omitted, resume at the present address. Success: return to the + executing thread. Failure: will never know. + + Should never be invoked. The single-step is implemented on + the host side. If ever invoked, it is an internal error E04. */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E04]); + putpacket (remcomOutBuffer); + return; + + case '?': + /* The last signal which caused a stop. ? + Success: SAA, where AA is the signal number. + Failure: void. */ + remcomOutBuffer[0] = 'S'; + remcomOutBuffer[1] = hex_asc_hi(sigval); + remcomOutBuffer[2] = hex_asc_lo(sigval); + remcomOutBuffer[3] = 0; + break; + + case 'D': + /* Detach from host. D + Success: OK, and return to the executing thread. + Failure: will never know */ + putpacket ("OK"); + return; + + case 'k': + case 'r': + /* kill request or reset request. + Success: restart of target. + Failure: will never know. */ + kill_restart (); + break; + + case 'C': + case 'S': + case '!': + case 'R': + case 'd': + /* Continue with signal sig. Csig;AA..AA + Step with signal sig. Ssig;AA..AA + Use the extended remote protocol. ! + Restart the target system. R0 + Toggle debug flag. d + Search backwards. tAA:PP,MM + Not supported: E04 */ + gdb_cris_strcpy (remcomOutBuffer, error_message[E04]); + break; + + default: + /* The stub should ignore other request and send an empty + response ($#<checksum>). This way we can extend the protocol and GDB + can tell whether the stub it is talking to uses the old or the new. */ + remcomOutBuffer[0] = 0; + break; + } + putpacket(remcomOutBuffer); + } +} + +/********************************** Breakpoint *******************************/ +/* The hook for both a static (compiled) and a dynamic breakpoint set by GDB. + An internal stack is used by the stub. The register image of the caller is + stored in the structure register_image. + Interactive communication with the host is handled by handle_exception and + finally the register image is restored. */ + +void kgdb_handle_breakpoint(void); + +asm ("\n" +" .global kgdb_handle_breakpoint\n" +"kgdb_handle_breakpoint:\n" +";;\n" +";; Response to the break-instruction\n" +";;\n" +";; Create a register image of the caller\n" +";;\n" +" move $dccr,[cris_reg+0x5E] ; Save the flags in DCCR before disable interrupts\n" +" di ; Disable interrupts\n" +" move.d $r0,[cris_reg] ; Save R0\n" +" move.d $r1,[cris_reg+0x04] ; Save R1\n" +" move.d $r2,[cris_reg+0x08] ; Save R2\n" +" move.d $r3,[cris_reg+0x0C] ; Save R3\n" +" move.d $r4,[cris_reg+0x10] ; Save R4\n" +" move.d $r5,[cris_reg+0x14] ; Save R5\n" +" move.d $r6,[cris_reg+0x18] ; Save R6\n" +" move.d $r7,[cris_reg+0x1C] ; Save R7\n" +" move.d $r8,[cris_reg+0x20] ; Save R8\n" +" move.d $r9,[cris_reg+0x24] ; Save R9\n" +" move.d $r10,[cris_reg+0x28] ; Save R10\n" +" move.d $r11,[cris_reg+0x2C] ; Save R11\n" +" move.d $r12,[cris_reg+0x30] ; Save R12\n" +" move.d $r13,[cris_reg+0x34] ; Save R13\n" +" move.d $sp,[cris_reg+0x38] ; Save SP (R14)\n" +";; Due to the old assembler-versions BRP might not be recognized\n" +" .word 0xE670 ; move brp,$r0\n" +" subq 2,$r0 ; Set to address of previous instruction.\n" +" move.d $r0,[cris_reg+0x3c] ; Save the address in PC (R15)\n" +" clear.b [cris_reg+0x40] ; Clear P0\n" +" move $vr,[cris_reg+0x41] ; Save special register P1\n" +" clear.w [cris_reg+0x42] ; Clear P4\n" +" move $ccr,[cris_reg+0x44] ; Save special register CCR\n" +" move $mof,[cris_reg+0x46] ; P7\n" +" clear.d [cris_reg+0x4A] ; Clear P8\n" +" move $ibr,[cris_reg+0x4E] ; P9,\n" +" move $irp,[cris_reg+0x52] ; P10,\n" +" move $srp,[cris_reg+0x56] ; P11,\n" +" move $dtp0,[cris_reg+0x5A] ; P12, register BAR, assembler might not know BAR\n" +" ; P13, register DCCR already saved\n" +";; Due to the old assembler-versions BRP might not be recognized\n" +" .word 0xE670 ; move brp,r0\n" +";; Static (compiled) breakpoints must return to the next instruction in order\n" +";; to avoid infinite loops. Dynamic (gdb-invoked) must restore the instruction\n" +";; in order to execute it when execution is continued.\n" +" test.b [is_dyn_brkp] ; Is this a dynamic breakpoint?\n" +" beq is_static ; No, a static breakpoint\n" +" nop\n" +" subq 2,$r0 ; rerun the instruction the break replaced\n" +"is_static:\n" +" moveq 1,$r1\n" +" move.b $r1,[is_dyn_brkp] ; Set the state variable to dynamic breakpoint\n" +" move.d $r0,[cris_reg+0x62] ; Save the return address in BRP\n" +" move $usp,[cris_reg+0x66] ; USP\n" +";;\n" +";; Handle the communication\n" +";;\n" +" move.d internal_stack+1020,$sp ; Use the internal stack which grows upward\n" +" moveq 5,$r10 ; SIGTRAP\n" +" jsr handle_exception ; Interactive routine\n" +";;\n" +";; Return to the caller\n" +";;\n" +" move.d [cris_reg],$r0 ; Restore R0\n" +" move.d [cris_reg+0x04],$r1 ; Restore R1\n" +" move.d [cris_reg+0x08],$r2 ; Restore R2\n" +" move.d [cris_reg+0x0C],$r3 ; Restore R3\n" +" move.d [cris_reg+0x10],$r4 ; Restore R4\n" +" move.d [cris_reg+0x14],$r5 ; Restore R5\n" +" move.d [cris_reg+0x18],$r6 ; Restore R6\n" +" move.d [cris_reg+0x1C],$r7 ; Restore R7\n" +" move.d [cris_reg+0x20],$r8 ; Restore R8\n" +" move.d [cris_reg+0x24],$r9 ; Restore R9\n" +" move.d [cris_reg+0x28],$r10 ; Restore R10\n" +" move.d [cris_reg+0x2C],$r11 ; Restore R11\n" +" move.d [cris_reg+0x30],$r12 ; Restore R12\n" +" move.d [cris_reg+0x34],$r13 ; Restore R13\n" +";;\n" +";; FIXME: Which registers should be restored?\n" +";;\n" +" move.d [cris_reg+0x38],$sp ; Restore SP (R14)\n" +" move [cris_reg+0x56],$srp ; Restore the subroutine return pointer.\n" +" move [cris_reg+0x5E],$dccr ; Restore DCCR\n" +" move [cris_reg+0x66],$usp ; Restore USP\n" +" jump [cris_reg+0x62] ; A jump to the content in register BRP works.\n" +" nop ;\n" +"\n"); + +/* The hook for an interrupt generated by GDB. An internal stack is used + by the stub. The register image of the caller is stored in the structure + register_image. Interactive communication with the host is handled by + handle_exception and finally the register image is restored. Due to the + old assembler which does not recognise the break instruction and the + breakpoint return pointer hex-code is used. */ + +void kgdb_handle_serial(void); + +asm ("\n" +" .global kgdb_handle_serial\n" +"kgdb_handle_serial:\n" +";;\n" +";; Response to a serial interrupt\n" +";;\n" +"\n" +" move $dccr,[cris_reg+0x5E] ; Save the flags in DCCR\n" +" di ; Disable interrupts\n" +" move.d $r0,[cris_reg] ; Save R0\n" +" move.d $r1,[cris_reg+0x04] ; Save R1\n" +" move.d $r2,[cris_reg+0x08] ; Save R2\n" +" move.d $r3,[cris_reg+0x0C] ; Save R3\n" +" move.d $r4,[cris_reg+0x10] ; Save R4\n" +" move.d $r5,[cris_reg+0x14] ; Save R5\n" +" move.d $r6,[cris_reg+0x18] ; Save R6\n" +" move.d $r7,[cris_reg+0x1C] ; Save R7\n" +" move.d $r8,[cris_reg+0x20] ; Save R8\n" +" move.d $r9,[cris_reg+0x24] ; Save R9\n" +" move.d $r10,[cris_reg+0x28] ; Save R10\n" +" move.d $r11,[cris_reg+0x2C] ; Save R11\n" +" move.d $r12,[cris_reg+0x30] ; Save R12\n" +" move.d $r13,[cris_reg+0x34] ; Save R13\n" +" move.d $sp,[cris_reg+0x38] ; Save SP (R14)\n" +" move $irp,[cris_reg+0x3c] ; Save the address in PC (R15)\n" +" clear.b [cris_reg+0x40] ; Clear P0\n" +" move $vr,[cris_reg+0x41] ; Save special register P1,\n" +" clear.w [cris_reg+0x42] ; Clear P4\n" +" move $ccr,[cris_reg+0x44] ; Save special register CCR\n" +" move $mof,[cris_reg+0x46] ; P7\n" +" clear.d [cris_reg+0x4A] ; Clear P8\n" +" move $ibr,[cris_reg+0x4E] ; P9,\n" +" move $irp,[cris_reg+0x52] ; P10,\n" +" move $srp,[cris_reg+0x56] ; P11,\n" +" move $dtp0,[cris_reg+0x5A] ; P12, register BAR, assembler might not know BAR\n" +" ; P13, register DCCR already saved\n" +";; Due to the old assembler-versions BRP might not be recognized\n" +" .word 0xE670 ; move brp,r0\n" +" move.d $r0,[cris_reg+0x62] ; Save the return address in BRP\n" +" move $usp,[cris_reg+0x66] ; USP\n" +"\n" +";; get the serial character (from debugport.c) and check if it is a ctrl-c\n" +"\n" +" jsr getDebugChar\n" +" cmp.b 3, $r10\n" +" bne goback\n" +" nop\n" +"\n" +" move.d [cris_reg+0x5E], $r10 ; Get DCCR\n" +" btstq 8, $r10 ; Test the U-flag.\n" +" bmi goback\n" +" nop\n" +"\n" +";;\n" +";; Handle the communication\n" +";;\n" +" move.d internal_stack+1020,$sp ; Use the internal stack\n" +" moveq 2,$r10 ; SIGINT\n" +" jsr handle_exception ; Interactive routine\n" +"\n" +"goback:\n" +";;\n" +";; Return to the caller\n" +";;\n" +" move.d [cris_reg],$r0 ; Restore R0\n" +" move.d [cris_reg+0x04],$r1 ; Restore R1\n" +" move.d [cris_reg+0x08],$r2 ; Restore R2\n" +" move.d [cris_reg+0x0C],$r3 ; Restore R3\n" +" move.d [cris_reg+0x10],$r4 ; Restore R4\n" +" move.d [cris_reg+0x14],$r5 ; Restore R5\n" +" move.d [cris_reg+0x18],$r6 ; Restore R6\n" +" move.d [cris_reg+0x1C],$r7 ; Restore R7\n" +" move.d [cris_reg+0x20],$r8 ; Restore R8\n" +" move.d [cris_reg+0x24],$r9 ; Restore R9\n" +" move.d [cris_reg+0x28],$r10 ; Restore R10\n" +" move.d [cris_reg+0x2C],$r11 ; Restore R11\n" +" move.d [cris_reg+0x30],$r12 ; Restore R12\n" +" move.d [cris_reg+0x34],$r13 ; Restore R13\n" +";;\n" +";; FIXME: Which registers should be restored?\n" +";;\n" +" move.d [cris_reg+0x38],$sp ; Restore SP (R14)\n" +" move [cris_reg+0x56],$srp ; Restore the subroutine return pointer.\n" +" move [cris_reg+0x5E],$dccr ; Restore DCCR\n" +" move [cris_reg+0x66],$usp ; Restore USP\n" +" reti ; Return from the interrupt routine\n" +" nop\n" +"\n"); + +/* Use this static breakpoint in the start-up only. */ + +void +breakpoint(void) +{ + kgdb_started = 1; + is_dyn_brkp = 0; /* This is a static, not a dynamic breakpoint. */ + __asm__ volatile ("break 8"); /* Jump to handle_breakpoint. */ +} + +/* initialize kgdb. doesn't break into the debugger, but sets up irq and ports */ + +void +kgdb_init(void) +{ + /* could initialize debug port as well but it's done in head.S already... */ + + /* breakpoint handler is now set in irq.c */ + set_int_vector(8, kgdb_handle_serial); + + enableDebugIRQ(); +} + +/****************************** End of file **********************************/ diff --git a/arch/cris/arch-v10/kernel/process.c b/arch/cris/arch-v10/kernel/process.c new file mode 100644 index 000000000..02b783457 --- /dev/null +++ b/arch/cris/arch-v10/kernel/process.c @@ -0,0 +1,193 @@ +/* + * linux/arch/cris/kernel/process.c + * + * Copyright (C) 1995 Linus Torvalds + * Copyright (C) 2000-2002 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * Mikael Starvik (starvik@axis.com) + * + * This file handles the architecture-dependent parts of process handling.. + */ + +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <arch/svinto.h> +#include <linux/init.h> +#include <arch/system.h> +#include <linux/ptrace.h> + +#ifdef CONFIG_ETRAX_GPIO +void etrax_gpio_wake_up_check(void); /* drivers/gpio.c */ +#endif + +/* + * We use this if we don't have any better + * idle routine.. + */ +void default_idle(void) +{ +#ifdef CONFIG_ETRAX_GPIO + etrax_gpio_wake_up_check(); +#endif + local_irq_enable(); +} + +/* + * Free current thread data structures etc.. + */ + +void exit_thread(void) +{ + /* Nothing needs to be done. */ +} + +/* if the watchdog is enabled, we can simply disable interrupts and go + * into an eternal loop, and the watchdog will reset the CPU after 0.1s + * if on the other hand the watchdog wasn't enabled, we just enable it and wait + */ + +void hard_reset_now (void) +{ + /* + * Don't declare this variable elsewhere. We don't want any other + * code to know about it than the watchdog handler in entry.S and + * this code, implementing hard reset through the watchdog. + */ +#if defined(CONFIG_ETRAX_WATCHDOG) + extern int cause_of_death; +#endif + + printk("*** HARD RESET ***\n"); + local_irq_disable(); + +#if defined(CONFIG_ETRAX_WATCHDOG) + cause_of_death = 0xbedead; +#else + /* Since we dont plan to keep on resetting the watchdog, + the key can be arbitrary hence three */ + *R_WATCHDOG = IO_FIELD(R_WATCHDOG, key, 3) | + IO_STATE(R_WATCHDOG, enable, start); +#endif + + while(1) /* waiting for RETRIBUTION! */ ; +} + +/* + * Return saved PC of a blocked thread. + */ +unsigned long thread_saved_pc(struct task_struct *t) +{ + return task_pt_regs(t)->irp; +} + +/* setup the child's kernel stack with a pt_regs and switch_stack on it. + * it will be un-nested during _resume and _ret_from_sys_call when the + * new thread is scheduled. + * + * also setup the thread switching structure which is used to keep + * thread-specific data during _resumes. + * + */ +asmlinkage void ret_from_fork(void); +asmlinkage void ret_from_kernel_thread(void); + +int copy_thread(unsigned long clone_flags, unsigned long usp, + unsigned long arg, struct task_struct *p) +{ + struct pt_regs *childregs = task_pt_regs(p); + struct switch_stack *swstack = ((struct switch_stack *)childregs) - 1; + + /* put the pt_regs structure at the end of the new kernel stack page and fix it up + * remember that the task_struct doubles as the kernel stack for the task + */ + + if (unlikely(p->flags & PF_KTHREAD)) { + memset(swstack, 0, + sizeof(struct switch_stack) + sizeof(struct pt_regs)); + swstack->r1 = usp; + swstack->r2 = arg; + childregs->dccr = 1 << I_DCCR_BITNR; + swstack->return_ip = (unsigned long) ret_from_kernel_thread; + p->thread.ksp = (unsigned long) swstack; + p->thread.usp = 0; + return 0; + } + *childregs = *current_pt_regs(); /* struct copy of pt_regs */ + + childregs->r10 = 0; /* child returns 0 after a fork/clone */ + + /* put the switch stack right below the pt_regs */ + + swstack->r9 = 0; /* parameter to ret_from_sys_call, 0 == dont restart the syscall */ + + /* we want to return into ret_from_sys_call after the _resume */ + + swstack->return_ip = (unsigned long) ret_from_fork; /* Will call ret_from_sys_call */ + + /* fix the user-mode stackpointer */ + + p->thread.usp = usp ?: rdusp(); + + /* and the kernel-mode one */ + + p->thread.ksp = (unsigned long) swstack; + +#ifdef DEBUG + printk("copy_thread: new regs at 0x%p, as shown below:\n", childregs); + show_registers(childregs); +#endif + + return 0; +} + +unsigned long get_wchan(struct task_struct *p) +{ +#if 0 + /* YURGH. TODO. */ + + unsigned long ebp, esp, eip; + unsigned long stack_page; + int count = 0; + if (!p || p == current || p->state == TASK_RUNNING) + return 0; + stack_page = (unsigned long)p; + esp = p->thread.esp; + if (!stack_page || esp < stack_page || esp > 8188+stack_page) + return 0; + /* include/asm-i386/system.h:switch_to() pushes ebp last. */ + ebp = *(unsigned long *) esp; + do { + if (ebp < stack_page || ebp > 8184+stack_page) + return 0; + eip = *(unsigned long *) (ebp+4); + if (!in_sched_functions(eip)) + return eip; + ebp = *(unsigned long *) ebp; + } while (count++ < 16); +#endif + return 0; +} +#undef last_sched +#undef first_sched + +void show_regs(struct pt_regs * regs) +{ + unsigned long usp = rdusp(); + + show_regs_print_info(KERN_DEFAULT); + + printk("IRP: %08lx SRP: %08lx DCCR: %08lx USP: %08lx MOF: %08lx\n", + regs->irp, regs->srp, regs->dccr, usp, regs->mof ); + printk(" r0: %08lx r1: %08lx r2: %08lx r3: %08lx\n", + regs->r0, regs->r1, regs->r2, regs->r3); + printk(" r4: %08lx r5: %08lx r6: %08lx r7: %08lx\n", + regs->r4, regs->r5, regs->r6, regs->r7); + printk(" r8: %08lx r9: %08lx r10: %08lx r11: %08lx\n", + regs->r8, regs->r9, regs->r10, regs->r11); + printk("r12: %08lx r13: %08lx oR10: %08lx\n", + regs->r12, regs->r13, regs->orig_r10); +} + diff --git a/arch/cris/arch-v10/kernel/ptrace.c b/arch/cris/arch-v10/kernel/ptrace.c new file mode 100644 index 000000000..bfddfb994 --- /dev/null +++ b/arch/cris/arch-v10/kernel/ptrace.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2000-2003, Axis Communications AB. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/user.h> +#include <linux/signal.h> +#include <linux/security.h> + +#include <asm/uaccess.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/processor.h> + +/* + * Determines which bits in DCCR the user has access to. + * 1 = access, 0 = no access. + */ +#define DCCR_MASK 0x0000001f /* XNZVC */ + +/* + * Get contents of register REGNO in task TASK. + */ +inline long get_reg(struct task_struct *task, unsigned int regno) +{ + /* USP is a special case, it's not in the pt_regs struct but + * in the tasks thread struct + */ + + if (regno == PT_USP) + return task->thread.usp; + else if (regno < PT_MAX) + return ((unsigned long *)task_pt_regs(task))[regno]; + else + return 0; +} + +/* + * Write contents of register REGNO in task TASK. + */ +inline int put_reg(struct task_struct *task, unsigned int regno, + unsigned long data) +{ + if (regno == PT_USP) + task->thread.usp = data; + else if (regno < PT_MAX) + ((unsigned long *)task_pt_regs(task))[regno] = data; + else + return -1; + return 0; +} + +/* + * Called by kernel/ptrace.c when detaching. + * + * Make sure the single step bit is not set. + */ +void +ptrace_disable(struct task_struct *child) +{ + /* Todo - pending singlesteps? */ + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); +} + +/* + * Note that this implementation of ptrace behaves differently from vanilla + * ptrace. Contrary to what the man page says, in the PTRACE_PEEKTEXT, + * PTRACE_PEEKDATA, and PTRACE_PEEKUSER requests the data variable is not + * ignored. Instead, the data variable is expected to point at a location + * (in user space) where the result of the ptrace call is written (instead of + * being returned). + */ +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + int ret; + unsigned int regno = addr >> 2; + unsigned long __user *datap = (unsigned long __user *)data; + + switch (request) { + /* Read word at location address. */ + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: + ret = generic_ptrace_peekdata(child, addr, data); + break; + + /* Read the word at location address in the USER area. */ + case PTRACE_PEEKUSR: { + unsigned long tmp; + + ret = -EIO; + if ((addr & 3) || regno > PT_MAX) + break; + + tmp = get_reg(child, regno); + ret = put_user(tmp, datap); + break; + } + + /* Write the word at location address. */ + case PTRACE_POKETEXT: + case PTRACE_POKEDATA: + ret = generic_ptrace_pokedata(child, addr, data); + break; + + /* Write the word at location address in the USER area. */ + case PTRACE_POKEUSR: + ret = -EIO; + if ((addr & 3) || regno > PT_MAX) + break; + + if (regno == PT_DCCR) { + /* don't allow the tracing process to change stuff like + * interrupt enable, kernel/user bit, dma enables etc. + */ + data &= DCCR_MASK; + data |= get_reg(child, PT_DCCR) & ~DCCR_MASK; + } + if (put_reg(child, regno, data)) + break; + ret = 0; + break; + + /* Get all GP registers from the child. */ + case PTRACE_GETREGS: { + int i; + unsigned long tmp; + + ret = 0; + for (i = 0; i <= PT_MAX; i++) { + tmp = get_reg(child, i); + + if (put_user(tmp, datap)) { + ret = -EFAULT; + break; + } + + datap++; + } + + break; + } + + /* Set all GP registers in the child. */ + case PTRACE_SETREGS: { + int i; + unsigned long tmp; + + ret = 0; + for (i = 0; i <= PT_MAX; i++) { + if (get_user(tmp, datap)) { + ret = -EFAULT; + break; + } + + if (i == PT_DCCR) { + tmp &= DCCR_MASK; + tmp |= get_reg(child, PT_DCCR) & ~DCCR_MASK; + } + + put_reg(child, i, tmp); + datap++; + } + + break; + } + + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +void do_syscall_trace(void) +{ + if (!test_thread_flag(TIF_SYSCALL_TRACE)) + return; + + if (!(current->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. + */ + if (current->exit_code) { + send_sig(current->exit_code, current, 1); + current->exit_code = 0; + } +} diff --git a/arch/cris/arch-v10/kernel/setup.c b/arch/cris/arch-v10/kernel/setup.c new file mode 100644 index 000000000..7ab31f1c7 --- /dev/null +++ b/arch/cris/arch-v10/kernel/setup.c @@ -0,0 +1,106 @@ +/* + * + * linux/arch/cris/arch-v10/kernel/setup.c + * + * Copyright (C) 1995 Linus Torvalds + * Copyright (c) 2001-2002 Axis Communications AB + */ + +/* + * This file handles the architecture-dependent parts of initialization + */ + +#include <linux/seq_file.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/param.h> +#include <arch/system.h> + +#ifdef CONFIG_PROC_FS +#define HAS_FPU 0x0001 +#define HAS_MMU 0x0002 +#define HAS_ETHERNET100 0x0004 +#define HAS_TOKENRING 0x0008 +#define HAS_SCSI 0x0010 +#define HAS_ATA 0x0020 +#define HAS_USB 0x0040 +#define HAS_IRQ_BUG 0x0080 +#define HAS_MMU_BUG 0x0100 + +static struct cpu_info { + char *model; + unsigned short cache; + unsigned short flags; +} cpu_info[] = { + /* The first four models will never ever run this code and are + only here for display. */ + { "ETRAX 1", 0, 0 }, + { "ETRAX 2", 0, 0 }, + { "ETRAX 3", 0, HAS_TOKENRING }, + { "ETRAX 4", 0, HAS_TOKENRING | HAS_SCSI }, + { "Unknown", 0, 0 }, + { "Unknown", 0, 0 }, + { "Unknown", 0, 0 }, + { "Simulator", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA }, + { "ETRAX 100", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_IRQ_BUG }, + { "ETRAX 100", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA }, + { "ETRAX 100LX", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_USB | HAS_MMU | HAS_MMU_BUG }, + { "ETRAX 100LX v2", 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_USB | HAS_MMU }, + { "Unknown", 0, 0 } /* This entry MUST be the last */ +}; + +int show_cpuinfo(struct seq_file *m, void *v) +{ + unsigned long revision; + struct cpu_info *info; + + /* read the version register in the CPU and print some stuff */ + + revision = rdvr(); + + if (revision >= ARRAY_SIZE(cpu_info)) + info = &cpu_info[ARRAY_SIZE(cpu_info) - 1]; + else + info = &cpu_info[revision]; + + seq_printf(m, + "processor\t: 0\n" + "cpu\t\t: CRIS\n" + "cpu revision\t: %lu\n" + "cpu model\t: %s\n" + "cache size\t: %d kB\n" + "fpu\t\t: %s\n" + "mmu\t\t: %s\n" + "mmu DMA bug\t: %s\n" + "ethernet\t: %s Mbps\n" + "token ring\t: %s\n" + "scsi\t\t: %s\n" + "ata\t\t: %s\n" + "usb\t\t: %s\n" + "bogomips\t: %lu.%02lu\n", + + revision, + info->model, + info->cache, + info->flags & HAS_FPU ? "yes" : "no", + info->flags & HAS_MMU ? "yes" : "no", + info->flags & HAS_MMU_BUG ? "yes" : "no", + info->flags & HAS_ETHERNET100 ? "10/100" : "10", + info->flags & HAS_TOKENRING ? "4/16 Mbps" : "no", + info->flags & HAS_SCSI ? "yes" : "no", + info->flags & HAS_ATA ? "yes" : "no", + info->flags & HAS_USB ? "yes" : "no", + (loops_per_jiffy * HZ + 500) / 500000, + ((loops_per_jiffy * HZ + 500) / 5000) % 100); + + return 0; +} + +#endif /* CONFIG_PROC_FS */ + +void +show_etrax_copyright(void) +{ + printk(KERN_INFO + "Linux/CRIS port on ETRAX 100LX (c) 2001 Axis Communications AB\n"); +} diff --git a/arch/cris/arch-v10/kernel/shadows.c b/arch/cris/arch-v10/kernel/shadows.c new file mode 100644 index 000000000..2454a0b02 --- /dev/null +++ b/arch/cris/arch-v10/kernel/shadows.c @@ -0,0 +1,36 @@ +/* + * Various shadow registers. Defines for these are in include/asm-etrax100/io.h + */ + +/* Shadows for internal Etrax-registers */ + +unsigned long genconfig_shadow; +unsigned long gen_config_ii_shadow; +unsigned long port_g_data_shadow; +unsigned char port_pa_dir_shadow; +unsigned char port_pa_data_shadow; +unsigned char port_pb_i2c_shadow; +unsigned char port_pb_config_shadow; +unsigned char port_pb_dir_shadow; +unsigned char port_pb_data_shadow; +unsigned long r_timer_ctrl_shadow; + +/* Shadows for external I/O port registers. + * These are only usable if there actually IS a latch connected + * to the corresponding external chip-select pin. + * + * A common usage is that CSP0 controls LEDs and CSP4 video chips. + */ + +unsigned long port_cse1_shadow; +unsigned long port_csp0_shadow; +unsigned long port_csp4_shadow; + +/* Corresponding addresses for the ports. + * These are initialized in arch/cris/mm/init.c using ioremap. + */ + +volatile unsigned long *port_cse1_addr; +volatile unsigned long *port_csp0_addr; +volatile unsigned long *port_csp4_addr; + diff --git a/arch/cris/arch-v10/kernel/signal.c b/arch/cris/arch-v10/kernel/signal.c new file mode 100644 index 000000000..7122d9773 --- /dev/null +++ b/arch/cris/arch-v10/kernel/signal.c @@ -0,0 +1,438 @@ +/* + * linux/arch/cris/kernel/signal.c + * + * Based on arch/i386/kernel/signal.c by + * Copyright (C) 1991, 1992 Linus Torvalds + * 1997-11-28 Modified for POSIX.1b signals by Richard Henderson * + * + * Ideas also taken from arch/arm. + * + * Copyright (C) 2000-2007 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * + */ + +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.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 <asm/processor.h> +#include <asm/ucontext.h> +#include <asm/uaccess.h> +#include <arch/system.h> + +#define DEBUG_SIG 0 + +/* a syscall in Linux/CRIS is a break 13 instruction which is 2 bytes */ +/* manipulate regs so that upon return, it will be re-executed */ + +/* We rely on that pc points to the instruction after "break 13", so the + * library must never do strange things like putting it in a delay slot. + */ +#define RESTART_CRIS_SYS(regs) regs->r10 = regs->orig_r10; regs->irp -= 2; + +void do_signal(int canrestart, struct pt_regs *regs); + +/* + * Do a signal return; undo the signal stack. + */ + +struct sigframe { + struct sigcontext sc; + unsigned long extramask[_NSIG_WORDS-1]; + unsigned char retcode[8]; /* trampoline code */ +}; + +struct rt_sigframe { + struct siginfo *pinfo; + void *puc; + struct siginfo info; + struct ucontext uc; + unsigned char retcode[8]; /* trampoline code */ +}; + + +static int +restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc) +{ + unsigned int err = 0; + unsigned long old_usp; + + /* Always make any pending restarted system calls return -EINTR */ + current->restart_block.fn = do_no_restart_syscall; + + /* restore the regs from &sc->regs (same as sc, since regs is first) + * (sc is already checked for VERIFY_READ since the sigframe was + * checked in sys_sigreturn previously) + */ + + if (__copy_from_user(regs, sc, sizeof(struct pt_regs))) + goto badframe; + + /* make sure the U-flag is set so user-mode cannot fool us */ + + regs->dccr |= 1 << 8; + + /* restore the old USP as it was before we stacked the sc etc. + * (we cannot just pop the sigcontext since we aligned the sp and + * stuff after pushing it) + */ + + err |= __get_user(old_usp, &sc->usp); + + wrusp(old_usp); + + /* TODO: the other ports use regs->orig_XX to disable syscall checks + * after this completes, but we don't use that mechanism. maybe we can + * use it now ? + */ + + return err; + +badframe: + return 1; +} + +asmlinkage int sys_sigreturn(void) +{ + struct pt_regs *regs = current_pt_regs(); + struct sigframe __user *frame = (struct sigframe *)rdusp(); + sigset_t set; + + /* + * Since we stacked the signal on a dword boundary, + * then frame should be dword aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (((long)frame) & 3) + goto badframe; + + 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; + + set_current_blocked(&set); + + if (restore_sigcontext(regs, &frame->sc)) + goto badframe; + + /* TODO: SIGTRAP when single-stepping as in arm ? */ + + return regs->r10; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +asmlinkage int sys_rt_sigreturn(void) +{ + struct pt_regs *regs = current_pt_regs(); + struct rt_sigframe __user *frame = (struct rt_sigframe *)rdusp(); + sigset_t set; + + /* + * Since we stacked the signal on a dword boundary, + * then frame should be dword aligned here. If it's + * not, then the user is trying to mess with us. + */ + if (((long)frame) & 3) + goto badframe; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + set_current_blocked(&set); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + goto badframe; + + if (restore_altstack(&frame->uc.uc_stack)) + goto badframe; + + return regs->r10; + +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; + unsigned long usp = rdusp(); + + /* copy the regs. they are first in sc so we can use sc directly */ + + err |= __copy_to_user(sc, regs, sizeof(struct pt_regs)); + + /* Set the frametype to CRIS_FRAME_NORMAL for the execution of + the signal handler. The frametype will be restored to its previous + value in restore_sigcontext. */ + regs->frametype = CRIS_FRAME_NORMAL; + + /* then some other stuff */ + + err |= __put_user(mask, &sc->oldmask); + + err |= __put_user(usp, &sc->usp); + + return err; +} + +/* Figure out where we want to put the new signal frame + * - usually on the stack. */ + +static inline void __user * +get_sigframe(struct ksignal *ksig, size_t frame_size) +{ + unsigned long sp = sigsp(rdusp(), ksig); + + /* make sure the frame is dword-aligned */ + + sp &= ~3; + + return (void __user*)(sp - frame_size); +} + +/* grab and setup a signal frame. + * + * basically we stack a lot of state info, and arrange for the + * user-mode program to return to the kernel using either a + * trampoline which performs the syscall sigreturn, or a provided + * user-mode trampoline. + */ + +static int setup_frame(struct ksignal *ksig, sigset_t *set, + struct pt_regs *regs) +{ + struct sigframe __user *frame; + unsigned long return_ip; + int err = 0; + + frame = get_sigframe(ksig, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + return -EFAULT; + + err |= setup_sigcontext(&frame->sc, regs, set->sig[0]); + if (err) + return -EFAULT; + + if (_NSIG_WORDS > 1) { + err |= __copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask)); + } + if (err) + return -EFAULT; + + /* Set up to return from userspace. If provided, use a stub + already in userspace. */ + if (ksig->ka.sa.sa_flags & SA_RESTORER) { + return_ip = (unsigned long)ksig->ka.sa.sa_restorer; + } else { + /* trampoline - the desired return ip is the retcode itself */ + return_ip = (unsigned long)&frame->retcode; + /* This is movu.w __NR_sigreturn, r9; break 13; */ + err |= __put_user(0x9c5f, (short __user*)(frame->retcode+0)); + err |= __put_user(__NR_sigreturn, (short __user*)(frame->retcode+2)); + err |= __put_user(0xe93d, (short __user*)(frame->retcode+4)); + } + + if (err) + return -EFAULT; + + /* Set up registers for signal handler */ + + regs->irp = (unsigned long) ksig->ka.sa.sa_handler; /* what we enter NOW */ + regs->srp = return_ip; /* what we enter LATER */ + regs->r10 = ksig->sig; /* first argument is signo */ + + /* actually move the usp to reflect the stacked frame */ + + wrusp((unsigned long)frame); + + return 0; +} + +static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, + struct pt_regs *regs) +{ + struct rt_sigframe __user *frame; + unsigned long return_ip; + int err = 0; + + frame = get_sigframe(ksig, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + return -EFAULT; + + err |= __put_user(&frame->info, &frame->pinfo); + err |= __put_user(&frame->uc, &frame->puc); + err |= copy_siginfo_to_user(&frame->info, &ksig->info); + if (err) + return -EFAULT; + + /* Clear all the bits of the ucontext we don't use. */ + err |= __clear_user(&frame->uc, offsetof(struct ucontext, uc_mcontext)); + + err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, set->sig[0]); + + err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + err |= __save_altstack(&frame->uc.uc_stack, rdusp()); + + if (err) + return -EFAULT; + + /* Set up to return from userspace. If provided, use a stub + already in userspace. */ + if (ksig->ka.sa.sa_flags & SA_RESTORER) { + return_ip = (unsigned long)ksig->ka.sa.sa_restorer; + } else { + /* trampoline - the desired return ip is the retcode itself */ + return_ip = (unsigned long)&frame->retcode; + /* This is movu.w __NR_rt_sigreturn, r9; break 13; */ + err |= __put_user(0x9c5f, (short __user *)(frame->retcode+0)); + err |= __put_user(__NR_rt_sigreturn, + (short __user *)(frame->retcode+2)); + err |= __put_user(0xe93d, (short __user *)(frame->retcode+4)); + } + + if (err) + return -EFAULT; + + /* Set up registers for signal handler */ + + /* What we enter NOW */ + regs->irp = (unsigned long) ksig->ka.sa.sa_handler; + /* What we enter LATER */ + regs->srp = return_ip; + /* First argument is signo */ + regs->r10 = ksig->sig; + /* Second argument is (siginfo_t *) */ + regs->r11 = (unsigned long)&frame->info; + /* Third argument is unused */ + regs->r12 = 0; + + /* Actually move the usp to reflect the stacked frame */ + wrusp((unsigned long)frame); + + return 0; +} + +/* + * OK, we're invoking a handler + */ + +static inline void handle_signal(int canrestart, struct ksignal *ksig, + struct pt_regs *regs) +{ + sigset_t *oldset = sigmask_to_save(); + int ret; + + /* Are we from a system call? */ + if (canrestart) { + /* If so, check system call restarting.. */ + switch (regs->r10) { + case -ERESTART_RESTARTBLOCK: + case -ERESTARTNOHAND: + /* ERESTARTNOHAND means that the syscall should + * only be restarted if there was no handler for + * the signal, and since we only get here if there + * is a handler, we don't restart */ + regs->r10 = -EINTR; + break; + case -ERESTARTSYS: + /* ERESTARTSYS means to restart the syscall if + * there is no handler or the handler was + * registered with SA_RESTART */ + if (!(ksig->ka.sa.sa_flags & SA_RESTART)) { + regs->r10 = -EINTR; + break; + } + /* fallthrough */ + case -ERESTARTNOINTR: + /* ERESTARTNOINTR means that the syscall should + * be called again after the signal handler returns. */ + RESTART_CRIS_SYS(regs); + } + } + + /* Set up the stack frame */ + if (ksig->ka.sa.sa_flags & SA_SIGINFO) + ret = setup_rt_frame(ksig, oldset, regs); + else + ret = setup_frame(ksig, oldset, regs); + + signal_setup_done(ret, ksig, 0); +} + +/* + * 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. + * + * Also note that the regs structure given here as an argument, is the latest + * pushed pt_regs. It may or may not be the same as the first pushed registers + * when the initial usermode->kernelmode transition took place. Therefore + * we can use user_mode(regs) to see if we came directly from kernel or user + * mode below. + */ + +void do_signal(int canrestart, struct pt_regs *regs) +{ + struct ksignal ksig; + + /* + * 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; + + if (get_signal(&ksig)) { + /* Whee! Actually deliver the signal. */ + handle_signal(canrestart, &ksig, regs); + return; + } + + /* Did we come from a system call? */ + if (canrestart) { + /* Restart the system call - no handlers present */ + if (regs->r10 == -ERESTARTNOHAND || + regs->r10 == -ERESTARTSYS || + regs->r10 == -ERESTARTNOINTR) { + RESTART_CRIS_SYS(regs); + } + if (regs->r10 == -ERESTART_RESTARTBLOCK) { + regs->r9 = __NR_restart_syscall; + regs->irp -= 2; + } + } + + /* if there's no signal to deliver, we just put the saved sigmask + * back */ + restore_saved_sigmask(); +} diff --git a/arch/cris/arch-v10/kernel/time.c b/arch/cris/arch-v10/kernel/time.c new file mode 100644 index 000000000..b5eb5cd2f --- /dev/null +++ b/arch/cris/arch-v10/kernel/time.c @@ -0,0 +1,267 @@ +/* + * linux/arch/cris/arch-v10/kernel/time.c + * + * Copyright (C) 1991, 1992, 1995 Linus Torvalds + * Copyright (C) 1999-2002 Axis Communications AB + * + */ + +#include <linux/timex.h> +#include <linux/time.h> +#include <linux/jiffies.h> +#include <linux/interrupt.h> +#include <linux/swap.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <asm/types.h> +#include <asm/signal.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/irq_regs.h> + +/* define this if you need to use print_timestamp */ +/* it will make jiffies at 96 hz instead of 100 hz though */ +#undef USE_CASCADE_TIMERS + +unsigned long get_ns_in_jiffie(void) +{ + unsigned char timer_count, t1; + unsigned short presc_count; + unsigned long ns; + unsigned long flags; + + local_irq_save(flags); + timer_count = *R_TIMER0_DATA; + presc_count = *R_TIM_PRESC_STATUS; + /* presc_count might be wrapped */ + t1 = *R_TIMER0_DATA; + + if (timer_count != t1){ + /* it wrapped, read prescaler again... */ + presc_count = *R_TIM_PRESC_STATUS; + timer_count = t1; + } + local_irq_restore(flags); + if (presc_count >= PRESCALE_VALUE/2 ){ + presc_count = PRESCALE_VALUE - presc_count + PRESCALE_VALUE/2; + } else { + presc_count = PRESCALE_VALUE - presc_count - PRESCALE_VALUE/2; + } + + ns = ( (TIMER0_DIV - timer_count) * ((1000000000/HZ)/TIMER0_DIV )) + + ( (presc_count) * (1000000000/PRESCALE_FREQ)); + return ns; +} + +static u32 cris_v10_gettimeoffset(void) +{ + u32 count; + + /* The timer interrupt comes from Etrax timer 0. In order to get + * better precision, we check the current value. It might have + * underflowed already though. + */ + count = *R_TIMER0_DATA; + + /* Convert timer value to nsec */ + return (TIMER0_DIV - count) * (NSEC_PER_SEC/HZ)/TIMER0_DIV; +} + +/* Excerpt from the Etrax100 HSDD about the built-in watchdog: + * + * 3.10.4 Watchdog timer + + * When the watchdog timer is started, it generates an NMI if the watchdog + * isn't restarted or stopped within 0.1 s. If it still isn't restarted or + * stopped after an additional 3.3 ms, the watchdog resets the chip. + * The watchdog timer is stopped after reset. The watchdog timer is controlled + * by the R_WATCHDOG register. The R_WATCHDOG register contains an enable bit + * and a 3-bit key value. The effect of writing to the R_WATCHDOG register is + * described in the table below: + * + * Watchdog Value written: + * state: To enable: To key: Operation: + * -------- ---------- ------- ---------- + * stopped 0 X No effect. + * stopped 1 key_val Start watchdog with key = key_val. + * started 0 ~key Stop watchdog + * started 1 ~key Restart watchdog with key = ~key. + * started X new_key_val Change key to new_key_val. + * + * Note: '~' is the bitwise NOT operator. + * + */ + +/* right now, starting the watchdog is the same as resetting it */ +#define start_watchdog reset_watchdog + +#ifdef CONFIG_ETRAX_WATCHDOG +static int watchdog_key = 0; /* arbitrary number */ +#endif + +/* number of pages to consider "out of memory". it is normal that the memory + * is used though, so put this really low. + */ + +#define WATCHDOG_MIN_FREE_PAGES 8 + +void reset_watchdog(void) +{ +#if defined(CONFIG_ETRAX_WATCHDOG) + /* only keep watchdog happy as long as we have memory left! */ + if(nr_free_pages() > WATCHDOG_MIN_FREE_PAGES) { + /* reset the watchdog with the inverse of the old key */ + watchdog_key ^= 0x7; /* invert key, which is 3 bits */ + *R_WATCHDOG = IO_FIELD(R_WATCHDOG, key, watchdog_key) | + IO_STATE(R_WATCHDOG, enable, start); + } +#endif +} + +/* stop the watchdog - we still need the correct key */ + +void stop_watchdog(void) +{ +#ifdef CONFIG_ETRAX_WATCHDOG + watchdog_key ^= 0x7; /* invert key, which is 3 bits */ + *R_WATCHDOG = IO_FIELD(R_WATCHDOG, key, watchdog_key) | + IO_STATE(R_WATCHDOG, enable, stop); +#endif +} + + +extern void cris_do_profile(struct pt_regs *regs); + +/* + * timer_interrupt() needs to keep up the real-time clock, + * as well as call the "xtime_update()" routine every clocktick + */ +static inline irqreturn_t timer_interrupt(int irq, void *dev_id) +{ + struct pt_regs *regs = get_irq_regs(); + /* acknowledge the timer irq */ + +#ifdef USE_CASCADE_TIMERS + *R_TIMER_CTRL = + IO_FIELD( R_TIMER_CTRL, timerdiv1, 0) | + IO_FIELD( R_TIMER_CTRL, timerdiv0, 0) | + IO_STATE( R_TIMER_CTRL, i1, clr) | + IO_STATE( R_TIMER_CTRL, tm1, run) | + IO_STATE( R_TIMER_CTRL, clksel1, cascade0) | + IO_STATE( R_TIMER_CTRL, i0, clr) | + IO_STATE( R_TIMER_CTRL, tm0, run) | + IO_STATE( R_TIMER_CTRL, clksel0, c6250kHz); +#else + *R_TIMER_CTRL = r_timer_ctrl_shadow | IO_STATE(R_TIMER_CTRL, i0, clr); +#endif + + /* reset watchdog otherwise it resets us! */ + reset_watchdog(); + + /* Update statistics. */ + update_process_times(user_mode(regs)); + + /* call the real timer interrupt handler */ + xtime_update(1); + + cris_do_profile(regs); /* Save profiling information */ + return IRQ_HANDLED; +} + +/* timer is IRQF_SHARED so drivers can add stuff to the timer irq chain */ + +static struct irqaction irq2 = { + .handler = timer_interrupt, + .flags = IRQF_SHARED, + .name = "timer", +}; + +void __init time_init(void) +{ + arch_gettimeoffset = cris_v10_gettimeoffset; + + /* probe for the RTC and read it if it exists + * Before the RTC can be probed the loops_per_usec variable needs + * to be initialized to make usleep work. A better value for + * loops_per_usec is calculated by the kernel later once the + * clock has started. + */ + loops_per_usec = 50; + + /* Setup the etrax timers + * Base frequency is 25000 hz, divider 250 -> 100 HZ + * In normal mode, we use timer0, so timer1 is free. In cascade + * mode (which we sometimes use for debugging) both timers are used. + * Remember that linux/timex.h contains #defines that rely on the + * timer settings below (hz and divide factor) !!! + */ + +#ifdef USE_CASCADE_TIMERS + *R_TIMER_CTRL = + IO_FIELD( R_TIMER_CTRL, timerdiv1, 0) | + IO_FIELD( R_TIMER_CTRL, timerdiv0, 0) | + IO_STATE( R_TIMER_CTRL, i1, nop) | + IO_STATE( R_TIMER_CTRL, tm1, stop_ld) | + IO_STATE( R_TIMER_CTRL, clksel1, cascade0) | + IO_STATE( R_TIMER_CTRL, i0, nop) | + IO_STATE( R_TIMER_CTRL, tm0, stop_ld) | + IO_STATE( R_TIMER_CTRL, clksel0, c6250kHz); + + *R_TIMER_CTRL = r_timer_ctrl_shadow = + IO_FIELD( R_TIMER_CTRL, timerdiv1, 0) | + IO_FIELD( R_TIMER_CTRL, timerdiv0, 0) | + IO_STATE( R_TIMER_CTRL, i1, nop) | + IO_STATE( R_TIMER_CTRL, tm1, run) | + IO_STATE( R_TIMER_CTRL, clksel1, cascade0) | + IO_STATE( R_TIMER_CTRL, i0, nop) | + IO_STATE( R_TIMER_CTRL, tm0, run) | + IO_STATE( R_TIMER_CTRL, clksel0, c6250kHz); +#else + *R_TIMER_CTRL = + IO_FIELD(R_TIMER_CTRL, timerdiv1, 192) | + IO_FIELD(R_TIMER_CTRL, timerdiv0, TIMER0_DIV) | + IO_STATE(R_TIMER_CTRL, i1, nop) | + IO_STATE(R_TIMER_CTRL, tm1, stop_ld) | + IO_STATE(R_TIMER_CTRL, clksel1, c19k2Hz) | + IO_STATE(R_TIMER_CTRL, i0, nop) | + IO_STATE(R_TIMER_CTRL, tm0, stop_ld) | + IO_STATE(R_TIMER_CTRL, clksel0, flexible); + + *R_TIMER_CTRL = r_timer_ctrl_shadow = + IO_FIELD(R_TIMER_CTRL, timerdiv1, 192) | + IO_FIELD(R_TIMER_CTRL, timerdiv0, TIMER0_DIV) | + IO_STATE(R_TIMER_CTRL, i1, nop) | + IO_STATE(R_TIMER_CTRL, tm1, run) | + IO_STATE(R_TIMER_CTRL, clksel1, c19k2Hz) | + IO_STATE(R_TIMER_CTRL, i0, nop) | + IO_STATE(R_TIMER_CTRL, tm0, run) | + IO_STATE(R_TIMER_CTRL, clksel0, flexible); + + *R_TIMER_PRESCALE = PRESCALE_VALUE; +#endif + + /* unmask the timer irq */ + *R_IRQ_MASK0_SET = IO_STATE(R_IRQ_MASK0_SET, timer0, set); + + /* now actually register the irq handler that calls timer_interrupt() */ + setup_irq(2, &irq2); /* irq 2 is the timer0 irq in etrax */ + + /* enable watchdog if we should use one */ +#if defined(CONFIG_ETRAX_WATCHDOG) + printk("Enabling watchdog...\n"); + start_watchdog(); + + /* If we use the hardware watchdog, we want to trap it as an NMI + and dump registers before it resets us. For this to happen, we + must set the "m" NMI enable flag (which once set, is unset only + when an NMI is taken). + + The same goes for the external NMI, but that doesn't have any + driver or infrastructure support yet. */ + asm ("setf m"); + + *R_IRQ_MASK0_SET = IO_STATE(R_IRQ_MASK0_SET, watchdog_nmi, set); + *R_VECT_MASK_SET = IO_STATE(R_VECT_MASK_SET, nmi, set); +#endif +} diff --git a/arch/cris/arch-v10/kernel/traps.c b/arch/cris/arch-v10/kernel/traps.c new file mode 100644 index 000000000..7001beda7 --- /dev/null +++ b/arch/cris/arch-v10/kernel/traps.c @@ -0,0 +1,131 @@ +/* + * Helper functions for trap handlers + * + * Copyright (C) 2000-2007, Axis Communications AB. + * + * Authors: Bjorn Wesen + * Hans-Peter Nilsson + * + */ + +#include <linux/ptrace.h> +#include <asm/uaccess.h> +#include <arch/sv_addr_ag.h> +#include <arch/system.h> + +void +show_registers(struct pt_regs *regs) +{ + /* + * It's possible to use either the USP register or current->thread.usp. + * USP might not correspond to the current process for all cases this + * function is called, and current->thread.usp isn't up to date for the + * current process. Experience shows that using USP is the way to go. + */ + unsigned long usp = rdusp(); + + printk("IRP: %08lx SRP: %08lx DCCR: %08lx USP: %08lx MOF: %08lx\n", + regs->irp, regs->srp, regs->dccr, usp, regs->mof); + + printk(" r0: %08lx r1: %08lx r2: %08lx r3: %08lx\n", + regs->r0, regs->r1, regs->r2, regs->r3); + + printk(" r4: %08lx r5: %08lx r6: %08lx r7: %08lx\n", + regs->r4, regs->r5, regs->r6, regs->r7); + + printk(" r8: %08lx r9: %08lx r10: %08lx r11: %08lx\n", + regs->r8, regs->r9, regs->r10, regs->r11); + + printk("r12: %08lx r13: %08lx oR10: %08lx sp: %08lx\n", + regs->r12, regs->r13, regs->orig_r10, (long unsigned)regs); + + printk("R_MMU_CAUSE: %08lx\n", (unsigned long)*R_MMU_CAUSE); + + printk("Process %s (pid: %d, stackpage=%08lx)\n", + current->comm, current->pid, (unsigned long)current); + + /* + * When in-kernel, we also print out the stack and code at the + * time of the fault.. + */ + if (!user_mode(regs)) { + int i; + + show_stack(NULL, (unsigned long *)usp); + + /* + * If the previous stack-dump wasn't a kernel one, dump the + * kernel stack now. + */ + if (usp != 0) + show_stack(NULL, NULL); + + printk("\nCode: "); + + if (regs->irp < PAGE_OFFSET) + goto bad_value; + + /* + * Quite often the value at regs->irp doesn't point to the + * interesting instruction, which often is the previous + * instruction. So dump at an offset large enough that the + * instruction decoding should be in sync at the interesting + * point, but small enough to fit on a row. The regs->irp + * location is pointed out in a ksymoops-friendly way by + * wrapping the byte for that address in parenthesises. + */ + for (i = -12; i < 12; i++) { + unsigned char c; + + if (__get_user(c, &((unsigned char *)regs->irp)[i])) { +bad_value: + printk(" Bad IP value."); + break; + } + + if (i == 0) + printk("(%02x) ", c); + else + printk("%02x ", c); + } + printk("\n"); + } +} + +void +arch_enable_nmi(void) +{ + asm volatile ("setf m"); +} + +extern void (*nmi_handler)(struct pt_regs *); +void handle_nmi(struct pt_regs *regs) +{ + if (nmi_handler) + nmi_handler(regs); + + /* Wait until nmi is no longer active. (We enable NMI immediately after + returning from this function, and we don't want it happening while + exiting from the NMI interrupt handler.) */ + while (*R_IRQ_MASK0_RD & IO_STATE(R_IRQ_MASK0_RD, nmi_pin, active)) + ; +} + +#ifdef CONFIG_DEBUG_BUGVERBOSE +void +handle_BUG(struct pt_regs *regs) +{ + struct bug_frame f; + unsigned char c; + unsigned long irp = regs->irp; + + if (__copy_from_user(&f, (const void __user *)(irp - 8), sizeof f)) + return; + if (f.prefix != BUG_PREFIX || f.magic != BUG_MAGIC) + return; + if (__get_user(c, f.filename)) + f.filename = "<bad filename>"; + + printk("kernel BUG at %s:%d!\n", f.filename, f.line); +} +#endif |