diff options
Diffstat (limited to 'arch/cris/arch-v32/kernel')
23 files changed, 9028 insertions, 0 deletions
diff --git a/arch/cris/arch-v32/kernel/Makefile b/arch/cris/arch-v32/kernel/Makefile new file mode 100644 index 0000000..5d5b613 --- /dev/null +++ b/arch/cris/arch-v32/kernel/Makefile @@ -0,0 +1,21 @@ +# $Id: Makefile,v 1.11 2004/12/17 10:16:13 starvik Exp $ +# +# Makefile for the linux kernel. +# + +extra-y := head.o + + +obj-y := entry.o traps.o irq.o debugport.o dma.o pinmux.o \ + process.o ptrace.o setup.o signal.o traps.o time.o \ + arbiter.o io.o + +obj-$(CONFIG_ETRAXFS_SIM) += vcs_hook.o + +obj-$(CONFIG_SMP) += smp.o +obj-$(CONFIG_ETRAX_KGDB) += kgdb.o kgdb_asm.o +obj-$(CONFIG_ETRAX_FAST_TIMER) += fasttimer.o +obj-$(CONFIG_MODULES) += crisksyms.o + +clean: + diff --git a/arch/cris/arch-v32/kernel/arbiter.c b/arch/cris/arch-v32/kernel/arbiter.c new file mode 100644 index 0000000..3870d2fd --- /dev/null +++ b/arch/cris/arch-v32/kernel/arbiter.c @@ -0,0 +1,297 @@ +/* + * Memory arbiter functions. Allocates bandwith through the + * arbiter and sets up arbiter breakpoints. + * + * The algorithm first assigns slots to the clients that has specified + * bandwith (e.g. ethernet) and then the remaining slots are divided + * on all the active clients. + * + * Copyright (c) 2004, 2005 Axis Communications AB. + */ + +#include <linux/config.h> +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/marb_defs.h> +#include <asm/arch/arbiter.h> +#include <asm/arch/hwregs/intr_vect.h> +#include <linux/interrupt.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <asm/io.h> + +struct crisv32_watch_entry +{ + unsigned long instance; + watch_callback* cb; + unsigned long start; + unsigned long end; + int used; +}; + +#define NUMBER_OF_BP 4 +#define NBR_OF_CLIENTS 14 +#define NBR_OF_SLOTS 64 +#define SDRAM_BANDWIDTH 100000000 /* Some kind of expected value */ +#define INTMEM_BANDWIDTH 400000000 +#define NBR_OF_REGIONS 2 + +static struct crisv32_watch_entry watches[NUMBER_OF_BP] = +{ + {regi_marb_bp0}, + {regi_marb_bp1}, + {regi_marb_bp2}, + {regi_marb_bp3} +}; + +static int requested_slots[NBR_OF_REGIONS][NBR_OF_CLIENTS]; +static int active_clients[NBR_OF_REGIONS][NBR_OF_CLIENTS]; +static int max_bandwidth[NBR_OF_REGIONS] = {SDRAM_BANDWIDTH, INTMEM_BANDWIDTH}; + +DEFINE_SPINLOCK(arbiter_lock); + +static irqreturn_t +crisv32_arbiter_irq(int irq, void* dev_id, struct pt_regs* regs); + +static void crisv32_arbiter_config(int region) +{ + int slot; + int client; + int interval = 0; + int val[NBR_OF_SLOTS]; + + for (slot = 0; slot < NBR_OF_SLOTS; slot++) + val[slot] = NBR_OF_CLIENTS + 1; + + for (client = 0; client < NBR_OF_CLIENTS; client++) + { + int pos; + if (!requested_slots[region][client]) + continue; + interval = NBR_OF_SLOTS / requested_slots[region][client]; + pos = 0; + while (pos < NBR_OF_SLOTS) + { + if (val[pos] != NBR_OF_CLIENTS + 1) + pos++; + else + { + val[pos] = client; + pos += interval; + } + } + } + + client = 0; + for (slot = 0; slot < NBR_OF_SLOTS; slot++) + { + if (val[slot] == NBR_OF_CLIENTS + 1) + { + int first = client; + while(!active_clients[region][client]) { + client = (client + 1) % NBR_OF_CLIENTS; + if (client == first) + break; + } + val[slot] = client; + client = (client + 1) % NBR_OF_CLIENTS; + } + if (region == EXT_REGION) + REG_WR_INT_VECT(marb, regi_marb, rw_ext_slots, slot, val[slot]); + else if (region == INT_REGION) + REG_WR_INT_VECT(marb, regi_marb, rw_int_slots, slot, val[slot]); + } +} + +extern char _stext, _etext; + +static void crisv32_arbiter_init(void) +{ + static int initialized = 0; + + if (initialized) + return; + + initialized = 1; + + /* CPU caches are active. */ + active_clients[EXT_REGION][10] = active_clients[EXT_REGION][11] = 1; + crisv32_arbiter_config(EXT_REGION); + crisv32_arbiter_config(INT_REGION); + + if (request_irq(MEMARB_INTR_VECT, crisv32_arbiter_irq, SA_INTERRUPT, + "arbiter", NULL)) + printk(KERN_ERR "Couldn't allocate arbiter IRQ\n"); + +#ifndef CONFIG_ETRAX_KGDB + /* Global watch for writes to kernel text segment. */ + crisv32_arbiter_watch(virt_to_phys(&_stext), &_etext - &_stext, + arbiter_all_clients, arbiter_all_write, NULL); +#endif +} + + + +int crisv32_arbiter_allocate_bandwith(int client, int region, + unsigned long bandwidth) +{ + int i; + int total_assigned = 0; + int total_clients = 0; + int req; + + crisv32_arbiter_init(); + + for (i = 0; i < NBR_OF_CLIENTS; i++) + { + total_assigned += requested_slots[region][i]; + total_clients += active_clients[region][i]; + } + req = NBR_OF_SLOTS / (max_bandwidth[region] / bandwidth); + + if (total_assigned + total_clients + req + 1 > NBR_OF_SLOTS) + return -ENOMEM; + + active_clients[region][client] = 1; + requested_slots[region][client] = req; + crisv32_arbiter_config(region); + + return 0; +} + +int crisv32_arbiter_watch(unsigned long start, unsigned long size, + unsigned long clients, unsigned long accesses, + watch_callback* cb) +{ + int i; + + crisv32_arbiter_init(); + + if (start > 0x80000000) { + printk("Arbiter: %lX doesn't look like a physical address", start); + return -EFAULT; + } + + spin_lock(&arbiter_lock); + + for (i = 0; i < NUMBER_OF_BP; i++) { + if (!watches[i].used) { + reg_marb_rw_intr_mask intr_mask = REG_RD(marb, regi_marb, rw_intr_mask); + + watches[i].used = 1; + watches[i].start = start; + watches[i].end = start + size; + watches[i].cb = cb; + + REG_WR_INT(marb_bp, watches[i].instance, rw_first_addr, watches[i].start); + REG_WR_INT(marb_bp, watches[i].instance, rw_last_addr, watches[i].end); + REG_WR_INT(marb_bp, watches[i].instance, rw_op, accesses); + REG_WR_INT(marb_bp, watches[i].instance, rw_clients, clients); + + if (i == 0) + intr_mask.bp0 = regk_marb_yes; + else if (i == 1) + intr_mask.bp1 = regk_marb_yes; + else if (i == 2) + intr_mask.bp2 = regk_marb_yes; + else if (i == 3) + intr_mask.bp3 = regk_marb_yes; + + REG_WR(marb, regi_marb, rw_intr_mask, intr_mask); + spin_unlock(&arbiter_lock); + + return i; + } + } + spin_unlock(&arbiter_lock); + return -ENOMEM; +} + +int crisv32_arbiter_unwatch(int id) +{ + reg_marb_rw_intr_mask intr_mask = REG_RD(marb, regi_marb, rw_intr_mask); + + crisv32_arbiter_init(); + + spin_lock(&arbiter_lock); + + if ((id < 0) || (id >= NUMBER_OF_BP) || (!watches[id].used)) { + spin_unlock(&arbiter_lock); + return -EINVAL; + } + + memset(&watches[id], 0, sizeof(struct crisv32_watch_entry)); + + if (id == 0) + intr_mask.bp0 = regk_marb_no; + else if (id == 1) + intr_mask.bp2 = regk_marb_no; + else if (id == 2) + intr_mask.bp2 = regk_marb_no; + else if (id == 3) + intr_mask.bp3 = regk_marb_no; + + REG_WR(marb, regi_marb, rw_intr_mask, intr_mask); + + spin_unlock(&arbiter_lock); + return 0; +} + +extern void show_registers(struct pt_regs *regs); + +static irqreturn_t +crisv32_arbiter_irq(int irq, void* dev_id, struct pt_regs* regs) +{ + reg_marb_r_masked_intr masked_intr = REG_RD(marb, regi_marb, r_masked_intr); + reg_marb_bp_r_brk_clients r_clients; + reg_marb_bp_r_brk_addr r_addr; + reg_marb_bp_r_brk_op r_op; + reg_marb_bp_r_brk_first_client r_first; + reg_marb_bp_r_brk_size r_size; + reg_marb_bp_rw_ack ack = {0}; + reg_marb_rw_ack_intr ack_intr = {.bp0=1,.bp1=1,.bp2=1,.bp3=1}; + struct crisv32_watch_entry* watch; + + if (masked_intr.bp0) { + watch = &watches[0]; + ack_intr.bp0 = regk_marb_yes; + } else if (masked_intr.bp1) { + watch = &watches[1]; + ack_intr.bp1 = regk_marb_yes; + } else if (masked_intr.bp2) { + watch = &watches[2]; + ack_intr.bp2 = regk_marb_yes; + } else if (masked_intr.bp3) { + watch = &watches[3]; + ack_intr.bp3 = regk_marb_yes; + } else { + return IRQ_NONE; + } + + /* Retrieve all useful information and print it. */ + r_clients = REG_RD(marb_bp, watch->instance, r_brk_clients); + r_addr = REG_RD(marb_bp, watch->instance, r_brk_addr); + r_op = REG_RD(marb_bp, watch->instance, r_brk_op); + r_first = REG_RD(marb_bp, watch->instance, r_brk_first_client); + r_size = REG_RD(marb_bp, watch->instance, r_brk_size); + + printk("Arbiter IRQ\n"); + printk("Clients %X addr %X op %X first %X size %X\n", + REG_TYPE_CONV(int, reg_marb_bp_r_brk_clients, r_clients), + REG_TYPE_CONV(int, reg_marb_bp_r_brk_addr, r_addr), + REG_TYPE_CONV(int, reg_marb_bp_r_brk_op, r_op), + REG_TYPE_CONV(int, reg_marb_bp_r_brk_first_client, r_first), + REG_TYPE_CONV(int, reg_marb_bp_r_brk_size, r_size)); + + REG_WR(marb_bp, watch->instance, rw_ack, ack); + REG_WR(marb, regi_marb, rw_ack_intr, ack_intr); + + printk("IRQ occured at %lX\n", regs->erp); + + if (watch->cb) + watch->cb(); + + + return IRQ_HANDLED; +} diff --git a/arch/cris/arch-v32/kernel/asm-offsets.c b/arch/cris/arch-v32/kernel/asm-offsets.c new file mode 100644 index 0000000..15b3d93 --- /dev/null +++ b/arch/cris/arch-v32/kernel/asm-offsets.c @@ -0,0 +1,49 @@ +#include <linux/sched.h> +#include <asm/thread_info.h> + +/* + * Generate definitions needed by assembly language modules. + * This code generates raw asm output which is post-processed to extract + * and format the required data. + */ + +#define DEFINE(sym, val) \ + asm volatile("\n->" #sym " %0 " #val : : "i" (val)) + +#define BLANK() asm volatile("\n->" : : ) + +int main(void) +{ +#define ENTRY(entry) DEFINE(PT_ ## entry, offsetof(struct pt_regs, entry)) + ENTRY(orig_r10); + ENTRY(r13); + ENTRY(r12); + ENTRY(r11); + ENTRY(r10); + ENTRY(r9); + ENTRY(acr); + ENTRY(srs); + ENTRY(mof); + ENTRY(ccs); + ENTRY(srp); + BLANK(); +#undef ENTRY +#define ENTRY(entry) DEFINE(TI_ ## entry, offsetof(struct thread_info, entry)) + ENTRY(task); + ENTRY(flags); + ENTRY(preempt_count); + BLANK(); +#undef ENTRY +#define ENTRY(entry) DEFINE(THREAD_ ## entry, offsetof(struct thread_struct, entry)) + ENTRY(ksp); + ENTRY(usp); + ENTRY(ccs); + BLANK(); +#undef ENTRY +#define ENTRY(entry) DEFINE(TASK_ ## entry, offsetof(struct task_struct, entry)) + ENTRY(pid); + BLANK(); + DEFINE(LCLONE_VM, CLONE_VM); + DEFINE(LCLONE_UNTRACED, CLONE_UNTRACED); + return 0; +} diff --git a/arch/cris/arch-v32/kernel/crisksyms.c b/arch/cris/arch-v32/kernel/crisksyms.c new file mode 100644 index 0000000..2c3bb9a --- /dev/null +++ b/arch/cris/arch-v32/kernel/crisksyms.c @@ -0,0 +1,24 @@ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/irq.h> +#include <asm/arch/dma.h> +#include <asm/arch/intmem.h> +#include <asm/arch/pinmux.h> + +/* Functions for allocating DMA channels */ +EXPORT_SYMBOL(crisv32_request_dma); +EXPORT_SYMBOL(crisv32_free_dma); + +/* Functions for handling internal RAM */ +EXPORT_SYMBOL(crisv32_intmem_alloc); +EXPORT_SYMBOL(crisv32_intmem_free); +EXPORT_SYMBOL(crisv32_intmem_phys_to_virt); +EXPORT_SYMBOL(crisv32_intmem_virt_to_phys); + +/* Functions for handling pinmux */ +EXPORT_SYMBOL(crisv32_pinmux_alloc); +EXPORT_SYMBOL(crisv32_pinmux_dealloc); + +/* Functions masking/unmasking interrupts */ +EXPORT_SYMBOL(mask_irq); +EXPORT_SYMBOL(unmask_irq); diff --git a/arch/cris/arch-v32/kernel/debugport.c b/arch/cris/arch-v32/kernel/debugport.c new file mode 100644 index 0000000..ffc1ebf --- /dev/null +++ b/arch/cris/arch-v32/kernel/debugport.c @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2003, Axis Communications AB. + */ + +#include <linux/config.h> +#include <linux/console.h> +#include <linux/init.h> +#include <linux/major.h> +#include <linux/delay.h> +#include <linux/tty.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/arch/hwregs/ser_defs.h> +#include <asm/arch/hwregs/dma_defs.h> +#include <asm/arch/pinmux.h> + +#include <asm/irq.h> +#include <asm/arch/hwregs/intr_vect_defs.h> + +struct dbg_port +{ + unsigned char nbr; + unsigned long instance; + unsigned int started; + unsigned long baudrate; + unsigned char parity; + unsigned int bits; +}; + +struct dbg_port ports[] = +{ + { + 0, + regi_ser0, + 0, + 115200, + 'N', + 8 + }, + { + 1, + regi_ser1, + 0, + 115200, + 'N', + 8 + }, + { + 2, + regi_ser2, + 0, + 115200, + 'N', + 8 + }, + { + 3, + regi_ser3, + 0, + 115200, + 'N', + 8 + } +}; +static 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 + +#ifdef CONFIG_ETRAX_KGDB +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 +#endif + +#ifdef CONFIG_ETRAXFS_SIM +extern void print_str( const char *str ); +static char buffer[1024]; +static char msg[] = "Debug: "; +static int buffer_pos = sizeof(msg) - 1; +#endif + +extern struct tty_driver *serial_driver; + +static void +start_port(struct dbg_port* p) +{ + if (!p) + return; + + if (p->started) + return; + p->started = 1; + + if (p->nbr == 1) + crisv32_pinmux_alloc_fixed(pinmux_ser1); + else if (p->nbr == 2) + crisv32_pinmux_alloc_fixed(pinmux_ser2); + else if (p->nbr == 3) + crisv32_pinmux_alloc_fixed(pinmux_ser3); + + /* Set up serial port registers */ + reg_ser_rw_tr_ctrl tr_ctrl = {0}; + reg_ser_rw_tr_dma_en tr_dma_en = {0}; + + reg_ser_rw_rec_ctrl rec_ctrl = {0}; + reg_ser_rw_tr_baud_div tr_baud_div = {0}; + reg_ser_rw_rec_baud_div rec_baud_div = {0}; + + tr_ctrl.base_freq = rec_ctrl.base_freq = regk_ser_f29_493; + tr_dma_en.en = rec_ctrl.dma_mode = regk_ser_no; + tr_baud_div.div = rec_baud_div.div = 29493000 / p->baudrate / 8; + tr_ctrl.en = rec_ctrl.en = 1; + + if (p->parity == 'O') + { + tr_ctrl.par_en = regk_ser_yes; + tr_ctrl.par = regk_ser_odd; + rec_ctrl.par_en = regk_ser_yes; + rec_ctrl.par = regk_ser_odd; + } + else if (p->parity == 'E') + { + tr_ctrl.par_en = regk_ser_yes; + tr_ctrl.par = regk_ser_even; + rec_ctrl.par_en = regk_ser_yes; + rec_ctrl.par = regk_ser_odd; + } + + if (p->bits == 7) + { + tr_ctrl.data_bits = regk_ser_bits7; + rec_ctrl.data_bits = regk_ser_bits7; + } + + REG_WR (ser, p->instance, rw_tr_baud_div, tr_baud_div); + REG_WR (ser, p->instance, rw_rec_baud_div, rec_baud_div); + REG_WR (ser, p->instance, rw_tr_dma_en, tr_dma_en); + REG_WR (ser, p->instance, rw_tr_ctrl, tr_ctrl); + REG_WR (ser, p->instance, rw_rec_ctrl, rec_ctrl); +} + +/* No debug */ +#ifdef CONFIG_ETRAX_DEBUG_PORT_NULL + +static void +console_write(struct console *co, const char *buf, unsigned int len) +{ + return; +} + +/* Target debug */ +#elif !defined(CONFIG_ETRAXFS_SIM) + +static void +console_write_direct(struct console *co, const char *buf, unsigned int len) +{ + int i; + reg_ser_r_stat_din stat; + reg_ser_rw_tr_dma_en tr_dma_en, old; + + /* Switch to manual mode */ + tr_dma_en = old = REG_RD (ser, port->instance, rw_tr_dma_en); + if (tr_dma_en.en == regk_ser_yes) { + tr_dma_en.en = regk_ser_no; + REG_WR(ser, port->instance, rw_tr_dma_en, tr_dma_en); + } + + /* Send data */ + for (i = 0; i < len; i++) { + /* LF -> CRLF */ + if (buf[i] == '\n') { + do { + stat = REG_RD (ser, port->instance, r_stat_din); + } while (!stat.tr_rdy); + REG_WR_INT (ser, port->instance, rw_dout, '\r'); + } + /* Wait until transmitter is ready and send.*/ + do { + stat = REG_RD (ser, port->instance, r_stat_din); + } while (!stat.tr_rdy); + REG_WR_INT (ser, port->instance, rw_dout, buf[i]); + } + + /* Restore mode */ + if (tr_dma_en.en != old.en) + REG_WR(ser, port->instance, rw_tr_dma_en, old); +} + +static void +console_write(struct console *co, const char *buf, unsigned int len) +{ + if (!port) + return; + console_write_direct(co, buf, len); +} + + + +#else + +/* VCS debug */ + +static void +console_write(struct console *co, const char *buf, unsigned int len) +{ + char* pos; + pos = memchr(buf, '\n', len); + if (pos) { + int l = ++pos - buf; + memcpy(buffer + buffer_pos, buf, l); + memcpy(buffer, msg, sizeof(msg) - 1); + buffer[buffer_pos + l] = '\0'; + print_str(buffer); + buffer_pos = sizeof(msg) - 1; + if (pos - buf != len) { + memcpy(buffer + buffer_pos, pos, len - l); + buffer_pos += len - l; + } + } else { + memcpy(buffer + buffer_pos, buf, len); + buffer_pos += len; + } +} + +#endif + +int raw_printk(const char *fmt, ...) +{ + static char buf[1024]; + int printed_len; + va_list args; + va_start(args, fmt); + printed_len = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + console_write(NULL, buf, strlen(buf)); + return printed_len; +} + +void +stupid_debug(char* buf) +{ + console_write(NULL, buf, strlen(buf)); +} + +#ifdef CONFIG_ETRAX_KGDB +/* Use polling to get a single character from the kernel debug port */ +int +getDebugChar(void) +{ + reg_ser_rs_status_data stat; + reg_ser_rw_ack_intr ack_intr = { 0 }; + + do { + stat = REG_RD(ser, kgdb_instance, rs_status_data); + } while (!stat.data_avail); + + /* Ack the data_avail interrupt. */ + ack_intr.data_avail = 1; + REG_WR(ser, kgdb_instance, rw_ack_intr, ack_intr); + + return stat.data; +} + +/* Use polling to put a single character to the kernel debug port */ +void +putDebugChar(int val) +{ + reg_ser_r_status_data stat; + do { + stat = REG_RD (ser, kgdb_instance, r_status_data); + } while (!stat.tr_ready); + REG_WR (ser, kgdb_instance, rw_data_out, REG_TYPE_CONV(reg_ser_rw_data_out, int, val)); +} +#endif /* CONFIG_ETRAX_KGDB */ + +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(port); + } + 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; +} + +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; + dummy_driver.init_termios.c_cflag = + B115200 | CS8 | CREAD | HUPCL | CLOCAL; /* is normally B9600 default... */ + dummy_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; + + dummy_driver.open = dummy_open; + dummy_driver.close = dummy_close; + dummy_driver.write = dummy_write; + dummy_driver.write_room = dummy_write_room; + if (tty_register_driver(&dummy_driver)) + panic("Couldn't register dummy serial driver\n"); +} + +static struct tty_driver* +crisv32_console_device(struct console* co, int *index) +{ + if (port) + *index = port->nbr; + return port ? serial_driver : &dummy_driver; +} + +static struct console sercons = { + name : "ttyS", + write: console_write, + read : NULL, + device : crisv32_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 : crisv32_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 : crisv32_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 : crisv32_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 : crisv32_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 /* CONFIG_ETRAX_KGDB */ + return 0; +} + +__initcall(init_etrax_debug); diff --git a/arch/cris/arch-v32/kernel/dma.c b/arch/cris/arch-v32/kernel/dma.c new file mode 100644 index 0000000..b92e857 --- /dev/null +++ b/arch/cris/arch-v32/kernel/dma.c @@ -0,0 +1,224 @@ +/* Wrapper for DMA channel allocator that starts clocks etc */ + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <asm/dma.h> +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/marb_defs.h> +#include <asm/arch/hwregs/config_defs.h> +#include <asm/arch/hwregs/strmux_defs.h> +#include <linux/errno.h> +#include <asm/system.h> +#include <asm/arch/arbiter.h> + +static char used_dma_channels[MAX_DMA_CHANNELS]; +static const char * used_dma_channels_users[MAX_DMA_CHANNELS]; + +static DEFINE_SPINLOCK(dma_lock); + +int crisv32_request_dma(unsigned int dmanr, const char * device_id, + unsigned options, unsigned int bandwidth, + enum dma_owner owner) +{ + unsigned long flags; + reg_config_rw_clk_ctrl clk_ctrl; + reg_strmux_rw_cfg strmux_cfg; + + if (crisv32_arbiter_allocate_bandwith(dmanr, + options & DMA_INT_MEM ? INT_REGION : EXT_REGION, + bandwidth)) + return -ENOMEM; + + spin_lock_irqsave(&dma_lock, flags); + + if (used_dma_channels[dmanr]) { + spin_unlock_irqrestore(&dma_lock, flags); + if (options & DMA_VERBOSE_ON_ERROR) { + printk("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; + } + clk_ctrl = REG_RD(config, regi_config, rw_clk_ctrl); + strmux_cfg = REG_RD(strmux, regi_strmux, rw_cfg); + + switch(dmanr) + { + case 0: + case 1: + clk_ctrl.dma01_eth0 = 1; + break; + case 2: + case 3: + clk_ctrl.dma23 = 1; + break; + case 4: + case 5: + clk_ctrl.dma45 = 1; + break; + case 6: + case 7: + clk_ctrl.dma67 = 1; + break; + case 8: + case 9: + clk_ctrl.dma89_strcop = 1; + break; +#if MAX_DMA_CHANNELS-1 != 9 +#error Check dma.c +#endif + default: + spin_unlock_irqrestore(&dma_lock, flags); + if (options & DMA_VERBOSE_ON_ERROR) { + printk("Failed to request DMA %i for %s, only 0-%i valid)\n", dmanr, device_id, MAX_DMA_CHANNELS-1); + } + + if (options & DMA_PANIC_ON_ERROR) + panic("request_dma error!"); + return -EINVAL; + } + + switch(owner) + { + case dma_eth0: + if (dmanr == 0) + strmux_cfg.dma0 = regk_strmux_eth0; + else if (dmanr == 1) + strmux_cfg.dma1 = regk_strmux_eth0; + else + panic("Invalid DMA channel for eth0\n"); + break; + case dma_eth1: + if (dmanr == 6) + strmux_cfg.dma6 = regk_strmux_eth1; + else if (dmanr == 7) + strmux_cfg.dma7 = regk_strmux_eth1; + else + panic("Invalid DMA channel for eth1\n"); + break; + case dma_iop0: + if (dmanr == 2) + strmux_cfg.dma2 = regk_strmux_iop0; + else if (dmanr == 3) + strmux_cfg.dma3 = regk_strmux_iop0; + else + panic("Invalid DMA channel for iop0\n"); + break; + case dma_iop1: + if (dmanr == 4) + strmux_cfg.dma4 = regk_strmux_iop1; + else if (dmanr == 5) + strmux_cfg.dma5 = regk_strmux_iop1; + else + panic("Invalid DMA channel for iop1\n"); + break; + case dma_ser0: + if (dmanr == 6) + strmux_cfg.dma6 = regk_strmux_ser0; + else if (dmanr == 7) + strmux_cfg.dma7 = regk_strmux_ser0; + else + panic("Invalid DMA channel for ser0\n"); + break; + case dma_ser1: + if (dmanr == 4) + strmux_cfg.dma4 = regk_strmux_ser1; + else if (dmanr == 5) + strmux_cfg.dma5 = regk_strmux_ser1; + else + panic("Invalid DMA channel for ser1\n"); + break; + case dma_ser2: + if (dmanr == 2) + strmux_cfg.dma2 = regk_strmux_ser2; + else if (dmanr == 3) + strmux_cfg.dma3 = regk_strmux_ser2; + else + panic("Invalid DMA channel for ser2\n"); + break; + case dma_ser3: + if (dmanr == 8) + strmux_cfg.dma8 = regk_strmux_ser3; + else if (dmanr == 9) + strmux_cfg.dma9 = regk_strmux_ser3; + else + panic("Invalid DMA channel for ser3\n"); + break; + case dma_sser0: + if (dmanr == 4) + strmux_cfg.dma4 = regk_strmux_sser0; + else if (dmanr == 5) + strmux_cfg.dma5 = regk_strmux_sser0; + else + panic("Invalid DMA channel for sser0\n"); + break; + case dma_sser1: + if (dmanr == 6) + strmux_cfg.dma6 = regk_strmux_sser1; + else if (dmanr == 7) + strmux_cfg.dma7 = regk_strmux_sser1; + else + panic("Invalid DMA channel for sser1\n"); + break; + case dma_ata: + if (dmanr == 2) + strmux_cfg.dma2 = regk_strmux_ata; + else if (dmanr == 3) + strmux_cfg.dma3 = regk_strmux_ata; + else + panic("Invalid DMA channel for ata\n"); + break; + case dma_strp: + if (dmanr == 8) + strmux_cfg.dma8 = regk_strmux_strcop; + else if (dmanr == 9) + strmux_cfg.dma9 = regk_strmux_strcop; + else + panic("Invalid DMA channel for strp\n"); + break; + case dma_ext0: + if (dmanr == 6) + strmux_cfg.dma6 = regk_strmux_ext0; + else + panic("Invalid DMA channel for ext0\n"); + break; + case dma_ext1: + if (dmanr == 7) + strmux_cfg.dma7 = regk_strmux_ext1; + else + panic("Invalid DMA channel for ext1\n"); + break; + case dma_ext2: + if (dmanr == 2) + strmux_cfg.dma2 = regk_strmux_ext2; + else if (dmanr == 8) + strmux_cfg.dma8 = regk_strmux_ext2; + else + panic("Invalid DMA channel for ext2\n"); + break; + case dma_ext3: + if (dmanr == 3) + strmux_cfg.dma3 = regk_strmux_ext3; + else if (dmanr == 9) + strmux_cfg.dma9 = regk_strmux_ext2; + else + panic("Invalid DMA channel for ext2\n"); + break; + } + + used_dma_channels[dmanr] = 1; + used_dma_channels_users[dmanr] = device_id; + REG_WR(config, regi_config, rw_clk_ctrl, clk_ctrl); + REG_WR(strmux, regi_strmux, rw_cfg, strmux_cfg); + spin_unlock_irqrestore(&dma_lock,flags); + return 0; +} + +void crisv32_free_dma(unsigned int dmanr) +{ + spin_lock(&dma_lock); + used_dma_channels[dmanr] = 0; + spin_unlock(&dma_lock); +} diff --git a/arch/cris/arch-v32/kernel/entry.S b/arch/cris/arch-v32/kernel/entry.S new file mode 100644 index 0000000..a8ed55e --- /dev/null +++ b/arch/cris/arch-v32/kernel/entry.S @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2000-2003 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * Tobias Anderberg (tobiasa@axis.com), CRISv32 port. + * + * Code for 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/config.h> +#include <linux/linkage.h> +#include <linux/sys.h> +#include <asm/unistd.h> +#include <asm/errno.h> +#include <asm/thread_info.h> +#include <asm/arch/offset.h> + +#include <asm/arch/hwregs/asm/reg_map_asm.h> +#include <asm/arch/hwregs/asm/intr_vect_defs_asm.h> + + ;; Exported functions. + .globl system_call + .globl ret_from_intr + .globl ret_from_fork + .globl resume + .globl multiple_interrupt + .globl nmi_interrupt + .globl spurious_interrupt + .globl do_sigtrap + .globl gdb_handle_exception + .globl sys_call_table + + ; Check if preemptive kernel scheduling should be done. +#ifdef CONFIG_PREEMPT +_resume_kernel: + di + ; Load current task struct. + movs.w -8192, $r0 ; THREAD_SIZE = 8192 + and.d $sp, $r0 + + addoq +TI_preempt_count, $r0, $acr + move.d [$acr], $r10 ; Preemption disabled? + bne _Rexit + nop + +_need_resched: + addoq +TI_flags, $r0, $acr + move.d [$acr], $r10 + btstq TIF_NEED_RESCHED, $r10 ; Check if need_resched is set. + bpl _Rexit + nop + + ; Do preemptive kernel scheduling. + jsr preempt_schedule_irq + nop + + ; 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 + nop + ba ret_from_sys_call + nop + +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. Don't simply + ;; test CCS since that doesn't necessarily reflect what mode we'll + ;; return into. + addoq +PT_ccs, $sp, $acr + move.d [$acr], $r0 + btstq 16, $r0 ; User-mode 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 + + addoq +TI_flags, $r0, $acr ; current->work + move.d [$acr], $r10 + and.d _TIF_WORK_MASK, $r10 ; Work to be done on return? + bne _work_pending + nop + ba _Rexit + nop + + ;; The system_call is called by a BREAK instruction, which looks pretty + ;; much like any other exception. + ;; + ;; System calls can't be made from interrupts but we still stack ERP + ;; to have a complete stack frame. + ;; + ;; 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. + subq 12, $sp ; Skip EXS, EDA. + move $erp, [$sp] + subq 4, $sp + move $srp, [$sp] + subq 4, $sp + move $ccs, [$sp] + subq 4, $sp + ei ; Allow IRQs while handling system call + move $spc, [$sp] + subq 4, $sp + move $mof, [$sp] + subq 4, $sp + move $srs, [$sp] + subq 4, $sp + move.d $acr, [$sp] + subq 14*4, $sp ; Make room for R0-R13. + movem $r13, [$sp] ; Push R0-R13 + subq 4, $sp + move.d $r10, [$sp] ; Push orig_r10. + +; Set S-bit when kernel debugging to keep hardware breakpoints active. +#ifdef CONFIG_ETRAX_KGDB + move $ccs, $r0 + or.d (1<<9), $r0 + move $r0, $ccs +#endif + + movs.w -ENOSYS, $r0 + addoq +PT_r10, $sp, $acr + move.d $r0, [$acr] + + ;; Check if this process is syscall-traced. + movs.w -8192, $r0 ; THREAD_SIZE == 8192 + and.d $sp, $r0 + + addoq +TI_flags, $r0, $acr + move.d [$acr], $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 + bhs ret_from_sys_call + lslq 2, $r9 ; Multiply by 4, in the delay slot. + + ;; The location on the stack for the register structure is passed as a + ;; seventh argument. Some system calls need this. + move.d $sp, $r0 + subq 4, $sp + move.d $r0, [$sp] + + ;; The registers carrying parameters (R10-R13) are intact. The optional + ;; fifth and sixth parameters is in MOF and SRP respectivly. Put them + ;; back on the stack. + subq 4, $sp + move $srp, [$sp] + subq 4, $sp + move $mof, [$sp] + + ;; Actually to the system call. + addo.d +sys_call_table, $r9, $acr + move.d [$acr], $acr + jsr $acr + nop + + addq 3*4, $sp ; Pop the mof, srp and regs parameters. + addoq +PT_r10, $sp, $acr + move.d $r10, [$acr] ; 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: + ;; >= 1 from syscall + ;; 0 from irq + + ;; Get the current task-struct pointer. + movs.w -8192, $r0 ; THREAD_SIZE == 8192 + and.d $sp, $r0 + + di ; Make sure need_resched and sigpending don't change. + + addoq +TI_flags, $r0, $acr + move.d [$acr], $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. + addq 4, $sp ; Skip orig_r10. + movem [$sp+], $r13 ; Registers R0-R13. + move.d [$sp+], $acr + move [$sp], $srs + addq 4, $sp + move [$sp+], $mof + move [$sp+], $spc + move [$sp+], $ccs + move [$sp+], $srp + move [$sp+], $erp + addq 8, $sp ; Skip EXS, EDA. + jump $erp + rfe ; Restore condition code stack in delay-slot. + + ;; 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. + + addoq +TI_flags, $r0, $acr + move.d [$acr], $r1 + btstq TIF_SYSCALL_TRACE, $r1 + bpl _work_pending + nop + ei + move.d $r9, $r1 ; Preserve R9. + jsr do_syscall_trace + nop + move.d $r1, $r9 + ba _resume_userspace + nop + +_work_pending: + addoq +TI_flags, $r0, $acr + move.d [$acr], $r10 + btstq TIF_NEED_RESCHED, $r10 ; Need resched? + bpl _work_notifysig ; No, must be signal/notify. + nop + +_work_resched: + move.d $r9, $r1 ; Preserve R9. + jsr schedule + nop + move.d $r1, $r9 + di + + addoq +TI_flags, $r0, $acr + move.d [$acr], $r1 + and.d _TIF_WORK_MASK, $r1 ; Ignore sycall 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. + + addoq +TI_flags, $r0, $acr + move.d [$acr], $r13 ; The thread_info_flags parameter. + move.d $r9, $r10 ; do_notify_resume syscall/irq param. + moveq 0, $r11 ; oldset param - 0 in this case. + move.d $sp, $r12 ; The regs param. + jsr do_notify_resume + nop + + 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 + nop + + ;; 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. + addoq +PT_r9, $sp, $acr + move.d [$acr], $r9 + addoq +PT_orig_r10, $sp, $acr + move.d [$acr], $r10 ; PT_r10 is already -ENOSYS. + addoq +PT_r11, $sp, $acr + move.d [$acr], $r11 + addoq +PT_r12, $sp, $acr + move.d [$acr], $r12 + addoq +PT_r13, $sp, $acr + move.d [$acr], $r13 + addoq +PT_mof, $sp, $acr + move [$acr], $mof + addoq +PT_srp, $sp, $acr + move [$acr], $srp + + ba _syscall_traced + nop + + ;; Resume performs the actual task-switching, by switching stack + ;; pointers. Input arguments are: + ;; + ;; R10 = prev + ;; R11 = next + ;; R12 = thread offset in task struct. + ;; + ;; Returns old current in R10. + +resume: + subq 4, $sp + move $srp, [$sp] ; Keep old/new PC on the stack. + add.d $r12, $r10 ; R10 = current tasks tss. + addoq +THREAD_ccs, $r10, $acr + move $ccs, [$acr] ; Save IRQ enable state. + di + + addoq +THREAD_usp, $r10, $acr + move $usp, [$acr] ; 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. + + addoq +THREAD_ksp, $r10, $acr + move.d $sp, [$acr] ; Save kernel SP for old task. + + move.d $sp, $r10 ; Return last running task in R10. + and.d -8192, $r10 ; Get thread_info from stackpointer. + addoq +TI_task, $r10, $acr + move.d [$acr], $r10 ; Get task. + add.d $r12, $r11 ; Find the new tasks tss. + addoq +THREAD_ksp, $r11, $acr + move.d [$acr], $sp ; Switch to new stackframe. + movem [$sp+], $r9 ; Restore non-scratch registers and R9. + + addoq +THREAD_usp, $r11, $acr + move [$acr], $usp ; Restore user-mode stackpointer. + + addoq +THREAD_ccs, $r11, $acr + move [$acr], $ccs ; Restore IRQ enable status. + move.d [$sp+], $acr + jump $acr ; Restore PC. + nop + +nmi_interrupt: + +;; If we receive a watchdog interrupt while it is not expected, then set +;; up a canonical frame and dump register contents before dying. + + ;; This prologue MUST match the one in irq.h and the struct in ptregs.h! + subq 12, $sp ; Skip EXS, EDA. + move $nrp, [$sp] + subq 4, $sp + move $srp, [$sp] + subq 4, $sp + move $ccs, [$sp] + subq 4, $sp + move $spc, [$sp] + subq 4, $sp + move $mof, [$sp] + subq 4, $sp + move $srs, [$sp] + subq 4, $sp + move.d $acr, [$sp] + subq 14*4, $sp ; Make room for R0-R13. + movem $r13, [$sp] ; Push R0-R13. + subq 4, $sp + move.d $r10, [$sp] ; Push orig_r10. + move.d REG_ADDR(intr_vect, regi_irq, r_nmi), $r0 + move.d [$r0], $r0 + btstq REG_BIT(intr_vect, r_nmi, watchdog), $r0 + bpl 1f + nop + jsr handle_watchdog_bite ; In time.c. + move.d $sp, $r10 ; Pointer to registers +1: btstq REG_BIT(intr_vect, r_nmi, ext), $r0 + bpl 1f + nop + jsr handle_nmi + move.d $sp, $r10 ; Pointer to registers +1: addq 4, $sp ; Skip orig_r10 + movem [$sp+], $r13 + move.d [$sp+], $acr + move [$sp], $srs + addq 4, $sp + move [$sp+], $mof + move [$sp+], $spc + move [$sp+], $ccs + move [$sp+], $srp + move [$sp+], $nrp + addq 8, $sp ; Skip EXS, EDA. + jump $nrp + rfn + + .comm cause_of_death, 4 ;; Don't declare this anywhere. + +spurious_interrupt: + di + jump hard_reset_now + nop + + ;; This handles the case when multiple interrupts arrive at the same + ;; time. Jump to the first set interrupt bit in a priotiry 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! + subq 12, $sp ; Skip EXS, EDA. + move $erp, [$sp] + subq 4, $sp + move $srp, [$sp] + subq 4, $sp + move $ccs, [$sp] + subq 4, $sp + move $spc, [$sp] + subq 4, $sp + move $mof, [$sp] + subq 4, $sp + move $srs, [$sp] + subq 4, $sp + move.d $acr, [$sp] + subq 14*4, $sp ; Make room for R0-R13. + movem $r13, [$sp] ; Push R0-R13. + subq 4, $sp + move.d $r10, [$sp] ; Push orig_r10. + +; Set S-bit when kernel debugging to keep hardware breakpoints active. +#ifdef CONFIG_ETRAX_KGDB + move $ccs, $r0 + or.d (1<<9), $r0 + move $r0, $ccs +#endif + + jsr crisv32_do_multiple + move.d $sp, $r10 + jump ret_from_intr + nop + +do_sigtrap: + ;; Sigtraps the process that executed the BREAK instruction. Creates a + ;; frame that Rexit expects. + subq 4, $sp + move $eda, [$sp] + subq 4, $sp + move $exs, [$sp] + subq 4, $sp + move $erp, [$sp] + subq 4, $sp + move $srp, [$sp] + subq 4, $sp + move $ccs, [$sp] + subq 4, $sp + move $spc, [$sp] + subq 4, $sp + move $mof, [$sp] + subq 4, $sp + move $srs, [$sp] + subq 4, $sp + move.d $acr, [$sp] + 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. + subq 4, $sp + move.d $r10, [$sp] ; Push orig_r10. + + movs.w -8192, $r9 ; THREAD_SIZE == 8192 + and.d $sp, $r9 + + ;; thread_info as first parameter + move.d $r9, $r10 + moveq 5, $r11 ; SIGTRAP as second argument. + jsr ugdb_trap_user + nop + jump ret_from_intr ; Use the return routine for interrupts. + nop + +gdb_handle_exception: + subq 4, $sp + move.d $r0, [$sp] +#ifdef CONFIG_ETRAX_KGDB + move $ccs, $r0 ; U-flag not affected by previous insns. + btstq 16, $r0 ; Test the U-flag. + bmi _ugdb_handle_exception ; Go to user mode debugging. + nop ; Empty delay-slot (cannot pop R0 here). + ba kgdb_handle_exception ; Go to kernel debugging. + move.d [$sp+], $r0 ; Restore R0 in delay slot. +#endif + +_ugdb_handle_exception: + ba do_sigtrap ; SIGTRAP the offending process. + move.d [$sp+], $r0 ; Restore R0 in delay slot. + + .data + + .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 old_readdir + .long old_mmap /* 90 */ + .long sys_munmap + .long sys_truncate + .long sys_ftruncate + .long sys_fchmod + .long sys_fchown16 /* 95 */ + .long sys_getpriority + .long sys_setpriority + .long sys_ni_syscall /* old profil syscall holder */ + .long sys_statfs + .long sys_fstatfs /* 100 */ + .long sys_ni_syscall /* 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 /* 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_nfsservctl + .long sys_setresgid16 /* 170 */ + .long sys_getresgid16 + .long sys_prctl + .long sys_rt_sigreturn + .long sys_rt_sigaction + .long sys_rt_sigprocmask /* 175 */ + .long sys_rt_sigpending + .long sys_rt_sigtimedwait + .long sys_rt_sigqueueinfo + .long sys_rt_sigsuspend + .long sys_pread64 /* 180 */ + .long sys_pwrite64 + .long sys_chown16 + .long sys_getcwd + .long sys_capget + .long sys_capset /* 185 */ + .long sys_sigaltstack + .long sys_sendfile + .long sys_ni_syscall /* streams1 */ + .long sys_ni_syscall /* streams2 */ + .long sys_vfork /* 190 */ + .long sys_getrlimit + .long sys_mmap2 + .long sys_truncate64 + .long sys_ftruncate64 + .long sys_stat64 /* 195 */ + .long sys_lstat64 + .long sys_fstat64 + .long sys_lchown + .long sys_getuid + .long sys_getgid /* 200 */ + .long sys_geteuid + .long sys_getegid + .long sys_setreuid + .long sys_setregid + .long sys_getgroups /* 205 */ + .long sys_setgroups + .long sys_fchown + .long sys_setresuid + .long sys_getresuid + .long sys_setresgid /* 210 */ + .long sys_getresgid + .long sys_chown + .long sys_setuid + .long sys_setgid + .long sys_setfsuid /* 215 */ + .long sys_setfsgid + .long sys_pivot_root + .long sys_mincore + .long sys_madvise + .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 /* reserved for kexec */ + .long sys_waitid + + /* + * 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-v32/kernel/fasttimer.c b/arch/cris/arch-v32/kernel/fasttimer.c new file mode 100644 index 0000000..ea2b4a9 --- /dev/null +++ b/arch/cris/arch-v32/kernel/fasttimer.c @@ -0,0 +1,996 @@ +/* $Id: fasttimer.c,v 1.11 2005/01/04 11:15:46 starvik Exp $ + * linux/arch/cris/kernel/fasttimer.c + * + * Fast timers for ETRAX FS + * This may be useful in other OS than Linux so use 2 space indentation... + * + * $Log: fasttimer.c,v $ + * Revision 1.11 2005/01/04 11:15:46 starvik + * Don't share timer IRQ. + * + * Revision 1.10 2004/12/07 09:19:38 starvik + * Corrected includes. + * Use correct interrupt macros. + * + * Revision 1.9 2004/05/14 10:18:58 starvik + * Export fast_timer_list + * + * Revision 1.8 2004/05/14 07:58:03 starvik + * Merge of changes from 2.4 + * + * Revision 1.7 2003/07/10 12:06:14 starvik + * Return IRQ_NONE if irq wasn't handled + * + * Revision 1.6 2003/07/04 08:27:49 starvik + * Merge of Linux 2.5.74 + * + * Revision 1.5 2003/06/05 10:16:22 johana + * New INTR_VECT macros. + * + * Revision 1.4 2003/06/03 08:49:45 johana + * Fixed typo. + * + * Revision 1.3 2003/06/02 12:51:27 johana + * Now compiles. + * Commented some include files that probably can be removed. + * + * Revision 1.2 2003/06/02 12:09:41 johana + * Ported to ETRAX FS using the trig interrupt instead of timer1. + * + * Revision 1.3 2002/12/12 08:26:32 starvik + * Don't use C-comments inside CVS comments + * + * Revision 1.2 2002/12/11 15:42:02 starvik + * Extracted v10 (ETRAX 100LX) specific stuff from arch/cris/kernel/ + * + * Revision 1.1 2002/11/18 07:58:06 starvik + * Fast timers (from Linux 2.4) + * + * Revision 1.5 2002/10/15 06:21:39 starvik + * Added call to init_waitqueue_head + * + * Revision 1.4 2002/05/28 17:47:59 johana + * Added del_fast_timer() + * + * Revision 1.3 2002/05/28 16:16:07 johana + * Handle empty fast_timer_list + * + * Revision 1.2 2002/05/27 15:38:42 johana + * Made it compile without warnings on Linux 2.4. + * (includes, wait_queue, PROC_FS and snprintf) + * + * Revision 1.1 2002/05/27 15:32:25 johana + * arch/etrax100/kernel/fasttimer.c v1.8 from the elinux tree. + * + * Revision 1.8 2001/11/27 13:50:40 pkj + * Disable interrupts while stopping the timer and while modifying the + * list of active timers in timer1_handler() as it may be interrupted + * by other interrupts (e.g., the serial interrupt) which may add fast + * timers. + * + * Revision 1.7 2001/11/22 11:50:32 pkj + * * Only store information about the last 16 timers. + * * proc_fasttimer_read() now uses an allocated buffer, since it + * requires more space than just a page even for only writing the + * last 16 timers. The buffer is only allocated on request, so + * unless /proc/fasttimer is read, it is never allocated. + * * Renamed fast_timer_started to fast_timers_started to match + * fast_timers_added and fast_timers_expired. + * * Some clean-up. + * + * Revision 1.6 2000/12/13 14:02:08 johana + * Removed volatile for fast_timer_list + * + * Revision 1.5 2000/12/13 13:55:35 johana + * Added DEBUG_LOG, added som cli() and cleanup + * + * Revision 1.4 2000/12/05 13:48:50 johana + * Added range check when writing proc file, modified timer int handling + * + * Revision 1.3 2000/11/23 10:10:20 johana + * More debug/logging possibilities. + * Moved GET_JIFFIES_USEC() to timex.h and time.c + * + * Revision 1.2 2000/11/01 13:41:04 johana + * Clean up and bugfixes. + * Created new do_gettimeofday_fast() that gets a timeval struct + * with time based on jiffies and *R_TIMER0_DATA, uses a table + * for fast conversion of timer value to microseconds. + * (Much faster the standard do_gettimeofday() and we don't really + * wan't to use the true time - we wan't the "uptime" so timers don't screw up + * when we change the time. + * TODO: Add efficient support for continuous timers as well. + * + * Revision 1.1 2000/10/26 15:49:16 johana + * Added fasttimer, highresolution timers. + * + * Copyright (C) 2000,2001 2002, 2003 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/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/delay.h> + +#include <asm/irq.h> +#include <asm/system.h> + +#include <linux/config.h> +#include <linux/version.h> + +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/timer_defs.h> +#include <asm/fasttimer.h> +#include <linux/proc_fs.h> + +/* + * timer0 is running at 100MHz and generating jiffies timer ticks + * at 100 or 1000 HZ. + * fasttimer gives an API that gives timers that expire "between" the jiffies + * giving microsecond resolution (10 ns). + * fasttimer uses reg_timer_rw_trig register to get interrupt when + * r_time reaches a certain value. + */ + + +#define DEBUG_LOG_INCLUDED +#define FAST_TIMER_LOG +//#define FAST_TIMER_TEST + +#define FAST_TIMER_SANITY_CHECKS + +#ifdef FAST_TIMER_SANITY_CHECKS +#define SANITYCHECK(x) x +static int sanity_failed = 0; +#else +#define SANITYCHECK(x) +#endif + +#define D1(x) +#define D2(x) +#define DP(x) + +#define __INLINE__ inline + +static int fast_timer_running = 0; +static int fast_timers_added = 0; +static int fast_timers_started = 0; +static int fast_timers_expired = 0; +static int fast_timers_deleted = 0; +static int fast_timer_is_init = 0; +static int fast_timer_ints = 0; + +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 int debug_log_cnt = 0; +static int debug_log_cnt_wrapped = 0; + +#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 + + +#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_delay_settings[NUM_TIMER_STATS]; + + +static void +timer_trig_handler(void); + + + +/* Not true gettimeofday, only checks the jiffies (uptime) + useconds */ +void __INLINE__ do_gettimeofday_fast(struct timeval *tv) +{ + unsigned long sec = jiffies; + unsigned long usec = GET_JIFFIES_USEC(); + + usec += (sec % HZ) * (1000000 / HZ); + sec = sec / HZ; + + if (usec > 1000000) + { + usec -= 1000000; + sec++; + } + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +int __INLINE__ timeval_cmp(struct timeval *t0, struct timeval *t1) +{ + if (t0->tv_sec < t1->tv_sec) + { + return -1; + } + else if (t0->tv_sec > t1->tv_sec) + { + return 1; + } + if (t0->tv_usec < t1->tv_usec) + { + return -1; + } + else if (t0->tv_usec > t1->tv_usec) + { + return 1; + } + return 0; +} + +/* Called with ints off */ +void __INLINE__ start_timer_trig(unsigned long delay_us) +{ + reg_timer_rw_ack_intr ack_intr = { 0 }; + reg_timer_rw_intr_mask intr_mask; + reg_timer_rw_trig trig; + reg_timer_rw_trig_cfg trig_cfg = { 0 }; + reg_timer_r_time r_time; + + r_time = REG_RD(timer, regi_timer, r_time); + + D1(printk("start_timer_trig : %d us freq: %i div: %i\n", + delay_us, freq_index, div)); + /* Clear trig irq */ + intr_mask = REG_RD(timer, regi_timer, rw_intr_mask); + intr_mask.trig = 0; + REG_WR(timer, regi_timer, rw_intr_mask, intr_mask); + + /* Set timer values */ + /* r_time is 100MHz (10 ns resolution) */ + trig = r_time + delay_us*(1000/10); + + timer_div_settings[fast_timers_started % NUM_TIMER_STATS] = trig; + timer_delay_settings[fast_timers_started % NUM_TIMER_STATS] = delay_us; + + /* Ack interrupt */ + ack_intr.trig = 1; + REG_WR(timer, regi_timer, rw_ack_intr, ack_intr); + + /* Start timer */ + REG_WR(timer, regi_timer, rw_trig, trig); + trig_cfg.tmr = regk_timer_time; + REG_WR(timer, regi_timer, rw_trig_cfg, trig_cfg); + + /* Check if we have already passed the trig time */ + r_time = REG_RD(timer, regi_timer, r_time); + if (r_time < trig) { + /* No, Enable trig irq */ + intr_mask = REG_RD(timer, regi_timer, rw_intr_mask); + intr_mask.trig = 1; + REG_WR(timer, regi_timer, rw_intr_mask, intr_mask); + fast_timers_started++; + fast_timer_running = 1; + } + else + { + /* We have passed the time, disable trig point, ack intr */ + trig_cfg.tmr = regk_timer_off; + REG_WR(timer, regi_timer, rw_trig_cfg, trig_cfg); + REG_WR(timer, regi_timer, rw_ack_intr, ack_intr); + /* call the int routine directly */ + timer_trig_handler(); + } + +} + +/* 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; + + SANITYCHECK({ /* Check so this is not in the list already... */ + while (tmp != NULL) + { + if (tmp == t) + { + printk("timer name: %s data: 0x%08lX already in list!\n", name, data); + sanity_failed++; + return; + } + else + { + tmp = tmp->next; + } + } + tmp = fast_timer_list; + }); + + 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_sec = t->tv_set.tv_sec + delay_us / 1000000; + if (t->tv_expires.tv_usec > 1000000) + { + t->tv_expires.tv_usec -= 1000000; + t->tv_expires.tv_sec++; + } +#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 || timeval_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_timer_trig(delay_us); + } else { + /* Put in correct place in list */ + while (tmp->next && + timeval_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)); + + 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 interrupt handler for trig interrupts */ + +static irqreturn_t +timer_trig_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + reg_timer_r_masked_intr masked_intr; + + /* Check if the timer interrupt is for us (a trig int) */ + masked_intr = REG_RD(timer, regi_timer, r_masked_intr); + if (!masked_intr.trig) + return IRQ_NONE; + timer_trig_handler(); + return IRQ_HANDLED; +} + +static void timer_trig_handler(void) +{ + reg_timer_rw_ack_intr ack_intr = { 0 }; + reg_timer_rw_intr_mask intr_mask; + reg_timer_rw_trig_cfg trig_cfg = { 0 }; + struct fast_timer *t; + unsigned long flags; + + local_irq_save(flags); + + /* Clear timer trig interrupt */ + intr_mask = REG_RD(timer, regi_timer, rw_intr_mask); + intr_mask.trig = 0; + REG_WR(timer, regi_timer, rw_intr_mask, intr_mask); + + /* First stop timer, then ack interrupt */ + /* Stop timer */ + trig_cfg.tmr = regk_timer_off; + REG_WR(timer, regi_timer, rw_trig_cfg, trig_cfg); + + /* Ack interrupt */ + ack_intr.trig = 1; + REG_WR(timer, regi_timer, rw_ack_intr, ack_intr); + + fast_timer_running = 0; + fast_timer_ints++; + + local_irq_restore(flags); + + t = fast_timer_list; + while (t) + { + struct timeval tv; + + /* Has it really expired? */ + do_gettimeofday_fast(&tv); + D1(printk("t: %is %06ius\n", tv.tv_sec, tv.tv_usec)); + + if (timeval_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 */ + local_irq_save(flags); + 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; + local_irq_restore(flags); + + if (t->function != NULL) + { + t->function(t->data); + } + else + { + DEBUG_LOG("!trimertrig %i function==NULL!\n", fast_timer_ints); + } + } + else + { + /* Timer is to early, let's set it again using the normal routines */ + D1(printk(".\n")); + } + + local_irq_save(flags); + if ((t = fast_timer_list) != NULL) + { + /* Start next timer.. */ + long us; + struct timeval tv; + + do_gettimeofday_fast(&tv); + us = ((t->tv_expires.tv_sec - tv.tv_sec) * 1000000 + + 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_timer_trig(us); + } + local_irq_restore(flags); + 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("ttrig stop!\n")); + } +} + +static void wake_up_func(unsigned long data) +{ +#ifdef DECLARE_WAITQUEUE + wait_queue_head_t *sleep_wait_p = (wait_queue_head_t*)data; +#else + struct wait_queue **sleep_wait_p = (struct wait_queue **)data; +#endif + wake_up(sleep_wait_p); +} + + +/* Useful API */ + +void schedule_usleep(unsigned long us) +{ + struct fast_timer t; +#ifdef DECLARE_WAITQUEUE + wait_queue_head_t sleep_wait; + init_waitqueue_head(&sleep_wait); + { + DECLARE_WAITQUEUE(wait, current); +#else + struct wait_queue *sleep_wait = NULL; + struct wait_queue wait = { current, NULL }; +#endif + + D1(printk("schedule_usleep(%d)\n", us)); + add_wait_queue(&sleep_wait, &wait); + set_current_state(TASK_INTERRUPTIBLE); + start_one_shot_timer(&t, wake_up_func, (unsigned long)&sleep_wait, us, + "usleep"); + schedule(); + set_current_state(TASK_RUNNING); + remove_wait_queue(&sleep_wait, &wait); + D1(printk("done schedule_usleep(%d)\n", us)); +#ifdef DECLARE_WAITQUEUE + } +#endif +} + +#ifdef CONFIG_PROC_FS +static int proc_fasttimer_read(char *buf, char **start, off_t offset, int len +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) + ,int *eof, void *data_unused +#else + ,int unused +#endif + ); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) +static struct proc_dir_entry *fasttimer_proc_entry; +#else +static struct proc_dir_entry fasttimer_proc_entry = +{ + 0, 9, "fasttimer", + S_IFREG | S_IRUGO, 1, 0, 0, + 0, NULL /* ops -- default to array */, + &proc_fasttimer_read /* get_info */, +}; +#endif +#endif /* CONFIG_PROC_FS */ + +#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_read(char *buf, char **start, off_t offset, int len +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) + ,int *eof, void *data_unused +#else + ,int unused +#endif + ) +{ + unsigned long flags; + int i = 0; + int num_to_show; + struct timeval tv; + struct fast_timer *t, *nextt; + static char *bigbuf = NULL; + static unsigned long used; + + if (!bigbuf && !(bigbuf = vmalloc(BIG_BUF_SIZE))) + { + used = 0; + bigbuf[0] = '\0'; + return 0; + } + + if (!offset || !used) + { + do_gettimeofday_fast(&tv); + + used = 0; + used += sprintf(bigbuf + used, "Fast timers added: %i\n", + fast_timers_added); + used += sprintf(bigbuf + used, "Fast timers started: %i\n", + fast_timers_started); + used += sprintf(bigbuf + used, "Fast timer interrupts: %i\n", + fast_timer_ints); + used += sprintf(bigbuf + used, "Fast timers expired: %i\n", + fast_timers_expired); + used += sprintf(bigbuf + used, "Fast timers deleted: %i\n", + fast_timers_deleted); + used += sprintf(bigbuf + used, "Fast timer running: %s\n", + fast_timer_running ? "yes" : "no"); + used += sprintf(bigbuf + used, "Current time: %lu.%06lu\n", + (unsigned long)tv.tv_sec, + (unsigned long)tv.tv_usec); +#ifdef FAST_TIMER_SANITY_CHECKS + used += sprintf(bigbuf + used, "Sanity failed: %i\n", + sanity_failed); +#endif + used += sprintf(bigbuf + used, "\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 && !used)) && + used+100 < BIG_BUF_SIZE) + { + used += sprintf(bigbuf + used, debug_log_string[i], + debug_log_value[i]); + i = (i+1) % DEBUG_LOG_MAX; + } + } + used += sprintf(bigbuf + used, "\n"); +#endif + + num_to_show = (fast_timers_started < NUM_TIMER_STATS ? fast_timers_started: + NUM_TIMER_STATS); + used += sprintf(bigbuf + used, "Timers started: %i\n", fast_timers_started); + for (i = 0; i < num_to_show && (used+100 < BIG_BUF_SIZE) ; i++) + { + int cur = (fast_timers_started - i - 1) % NUM_TIMER_STATS; + +#if 1 //ndef FAST_TIMER_LOG + used += sprintf(bigbuf + used, "div: %i delay: %i" + "\n", + timer_div_settings[cur], + timer_delay_settings[cur] + ); +#endif +#ifdef FAST_TIMER_LOG + t = &timer_started_log[cur]; + used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu " + "d: %6li us data: 0x%08lX" + "\n", + t->name, + (unsigned long)t->tv_set.tv_sec, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_sec, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data + ); +#endif + } + used += sprintf(bigbuf + used, "\n"); + +#ifdef FAST_TIMER_LOG + num_to_show = (fast_timers_added < NUM_TIMER_STATS ? fast_timers_added: + NUM_TIMER_STATS); + used += sprintf(bigbuf + used, "Timers added: %i\n", fast_timers_added); + for (i = 0; i < num_to_show && (used+100 < BIG_BUF_SIZE); i++) + { + t = &timer_added_log[(fast_timers_added - i - 1) % NUM_TIMER_STATS]; + used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu " + "d: %6li us data: 0x%08lX" + "\n", + t->name, + (unsigned long)t->tv_set.tv_sec, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_sec, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data + ); + } + used += sprintf(bigbuf + used, "\n"); + + num_to_show = (fast_timers_expired < NUM_TIMER_STATS ? fast_timers_expired: + NUM_TIMER_STATS); + used += sprintf(bigbuf + used, "Timers expired: %i\n", fast_timers_expired); + for (i = 0; i < num_to_show && (used+100 < BIG_BUF_SIZE); i++) + { + t = &timer_expired_log[(fast_timers_expired - i - 1) % NUM_TIMER_STATS]; + used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu " + "d: %6li us data: 0x%08lX" + "\n", + t->name, + (unsigned long)t->tv_set.tv_sec, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_sec, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data + ); + } + used += sprintf(bigbuf + used, "\n"); +#endif + + used += sprintf(bigbuf + used, "Active timers:\n"); + local_irq_save(flags); + local_irq_save(flags); + t = fast_timer_list; + while (t != NULL && (used+100 < BIG_BUF_SIZE)) + { + nextt = t->next; + local_irq_restore(flags); + used += sprintf(bigbuf + used, "%-14s s: %6lu.%06lu e: %6lu.%06lu " + "d: %6li us data: 0x%08lX" +/* " func: 0x%08lX" */ + "\n", + t->name, + (unsigned long)t->tv_set.tv_sec, + (unsigned long)t->tv_set.tv_usec, + (unsigned long)t->tv_expires.tv_sec, + (unsigned long)t->tv_expires.tv_usec, + t->delay_us, + t->data +/* , t->function */ + ); + local_irq_disable(); + if (t->next != nextt) + { + printk("timer removed!\n"); + } + t = nextt; + } + local_irq_restore(flags); + } + + if (used - offset < len) + { + len = used - offset; + } + + memcpy(buf, bigbuf + offset, len); + *start = buf; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) + *eof = 1; +#endif + + return len; +} +#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 timeval 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 timeval 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("fast_timer_test() %is %06i\n", tv.tv_sec, 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("%i.%i %i.%i %i.%i %i.%i %i.%i\n", + tv_exp[j].tv_sec,tv_exp[j].tv_usec, + tv_exp[j+1].tv_sec,tv_exp[j+1].tv_usec, + tv_exp[j+2].tv_sec,tv_exp[j+2].tv_usec, + tv_exp[j+3].tv_sec,tv_exp[j+3].tv_usec, + tv_exp[j+4].tv_sec,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("Timers started %is %06i\n", tv0.tv_sec, tv0.tv_usec); + printk("Timers started at %is %06i\n", tv1.tv_sec, tv1.tv_usec); + printk("Timers done %is %06i\n", tv2.tv_sec, 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_sec, + t->tv_set.tv_usec, + t->tv_expires.tv_sec, + 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_sec, + tv_exp[j].tv_usec, + exp_num[j], + (tv_exp[j].tv_sec - t->tv_expires.tv_sec)*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 + + +void fast_timer_init(void) +{ + /* For some reason, request_irq() hangs when called froom time_init() */ + if (!fast_timer_is_init) + { + printk("fast_timer_init()\n"); + +#ifdef CONFIG_PROC_FS +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0) + if ((fasttimer_proc_entry = create_proc_entry( "fasttimer", 0, 0 ))) + fasttimer_proc_entry->read_proc = proc_fasttimer_read; +#else + proc_register_dynamic(&proc_root, &fasttimer_proc_entry); +#endif +#endif /* PROC_FS */ + if(request_irq(TIMER_INTR_VECT, timer_trig_interrupt, SA_INTERRUPT, + "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 + } +} diff --git a/arch/cris/arch-v32/kernel/head.S b/arch/cris/arch-v32/kernel/head.S new file mode 100644 index 0000000..3cfe57d --- /dev/null +++ b/arch/cris/arch-v32/kernel/head.S @@ -0,0 +1,448 @@ +/* + * CRISv32 kernel startup code. + * + * Copyright (C) 2003, Axis Communications AB + */ + +#include <linux/config.h> + +#define ASSEMBLER_MACROS_ONLY + +/* + * The macros found in mmu_defs_asm.h uses the ## concatenation operator, so + * -traditional must not be used when assembling this file. + */ +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/asm/mmu_defs_asm.h> +#include <asm/arch/hwregs/asm/reg_map_asm.h> +#include <asm/arch/hwregs/asm/config_defs_asm.h> +#include <asm/arch/hwregs/asm/bif_core_defs_asm.h> + +#define CRAMFS_MAGIC 0x28cd3d45 +#define RAM_INIT_MAGIC 0x56902387 +#define COMMAND_LINE_MAGIC 0x87109563 + + ;; NOTE: R8 and R9 carry information from the decompressor (if the + ;; kernel was compressed). They must not be used in the code below + ;; until they are read! + + ;; Exported symbols. + .global etrax_irv + .global romfs_start + .global romfs_length + .global romfs_in_flash + .global swapper_pg_dir + .global crisv32_nand_boot + .global crisv32_nand_cramfs_offset + + ;; Dummy section to make it bootable with current VCS simulator +#ifdef CONFIG_ETRAXFS_SIM + .section ".boot", "ax" + ba tstart + nop +#endif + + .text +tstart: + ;; This is the entry point of the kernel. The CPU is currently in + ;; supervisor mode. + ;; + ;; 0x00000000 if flash. + ;; 0x40004000 if DRAM. + ;; + di + + ;; Start clocks for used blocks. + move.d REG_ADDR(config, regi_config, rw_clk_ctrl), $r1 + move.d [$r1], $r0 + or.d REG_STATE(config, rw_clk_ctrl, cpu, yes) | \ + REG_STATE(config, rw_clk_ctrl, bif, yes) | \ + REG_STATE(config, rw_clk_ctrl, fix_io, yes), $r0 + move.d $r0, [$r1] + + ;; Set up waitstates etc + move.d REG_ADDR(bif_core, regi_bif_core, rw_grp1_cfg), $r0 + move.d CONFIG_ETRAX_MEM_GRP1_CONFIG, $r1 + move.d $r1, [$r0] + move.d REG_ADDR(bif_core, regi_bif_core, rw_grp2_cfg), $r0 + move.d CONFIG_ETRAX_MEM_GRP2_CONFIG, $r1 + move.d $r1, [$r0] + move.d REG_ADDR(bif_core, regi_bif_core, rw_grp3_cfg), $r0 + move.d CONFIG_ETRAX_MEM_GRP3_CONFIG, $r1 + move.d $r1, [$r0] + move.d REG_ADDR(bif_core, regi_bif_core, rw_grp4_cfg), $r0 + move.d CONFIG_ETRAX_MEM_GRP4_CONFIG, $r1 + move.d $r1, [$r0] + +#ifdef CONFIG_ETRAXFS_SIM + ;; Set up minimal flash waitstates + move.d 0, $r10 + move.d REG_ADDR(bif_core, regi_bif_core, rw_grp1_cfg), $r11 + move.d $r10, [$r11] +#endif + + ;; Setup and enable the MMU. Use same configuration for both the data + ;; and the instruction MMU. + ;; + ;; Note; 3 cycles is needed for a bank-select to take effect. Further; + ;; bank 1 is the instruction MMU, bank 2 is the data MMU. +#ifndef CONFIG_ETRAXFS_SIM + move.d REG_FIELD(mmu, rw_mm_kbase_hi, base_e, 8) \ + | REG_FIELD(mmu, rw_mm_kbase_hi, base_c, 4) \ + | REG_FIELD(mmu, rw_mm_kbase_hi, base_b, 0xb), $r0 +#else + ;; Map the virtual DRAM to the RW eprom area at address 0. + ;; Also map 0xa for the hook calls, + move.d REG_FIELD(mmu, rw_mm_kbase_hi, base_e, 8) \ + | REG_FIELD(mmu, rw_mm_kbase_hi, base_c, 0) \ + | REG_FIELD(mmu, rw_mm_kbase_hi, base_b, 0xb) \ + | REG_FIELD(mmu, rw_mm_kbase_hi, base_a, 0xa), $r0 +#endif + + ;; Temporary map of 0x40 -> 0x40 and 0x00 -> 0x00. + move.d REG_FIELD(mmu, rw_mm_kbase_lo, base_4, 4) \ + | REG_FIELD(mmu, rw_mm_kbase_lo, base_0, 0), $r1 + + ;; Enable certain page protections and setup linear mapping + ;; for f,e,c,b,4,0. +#ifndef CONFIG_ETRAXFS_SIM + move.d REG_STATE(mmu, rw_mm_cfg, we, on) \ + | REG_STATE(mmu, rw_mm_cfg, acc, on) \ + | REG_STATE(mmu, rw_mm_cfg, ex, on) \ + | REG_STATE(mmu, rw_mm_cfg, inv, on) \ + | REG_STATE(mmu, rw_mm_cfg, seg_f, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_e, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_d, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_c, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_b, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_a, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_9, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_8, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_7, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_6, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_5, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_4, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_3, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_2, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_1, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_0, linear), $r2 +#else + move.d REG_STATE(mmu, rw_mm_cfg, we, on) \ + | REG_STATE(mmu, rw_mm_cfg, acc, on) \ + | REG_STATE(mmu, rw_mm_cfg, ex, on) \ + | REG_STATE(mmu, rw_mm_cfg, inv, on) \ + | REG_STATE(mmu, rw_mm_cfg, seg_f, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_e, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_d, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_c, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_b, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_a, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_9, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_8, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_7, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_6, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_5, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_4, linear) \ + | REG_STATE(mmu, rw_mm_cfg, seg_3, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_2, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_1, page) \ + | REG_STATE(mmu, rw_mm_cfg, seg_0, linear), $r2 +#endif + + ;; Update instruction MMU. + move 1, $srs + nop + nop + nop + move $r0, $s2 ; kbase_hi. + move $r1, $s1 ; kbase_lo. + move $r2, $s0 ; mm_cfg, virtual memory configuration. + + ;; Update data MMU. + move 2, $srs + nop + nop + nop + move $r0, $s2 ; kbase_hi. + move $r1, $s1 ; kbase_lo + move $r2, $s0 ; mm_cfg, virtual memory configuration. + + ;; Enable data and instruction MMU. + move 0, $srs + moveq 0xf, $r0 ; IMMU, DMMU, DCache, Icache on + nop + nop + nop + move $r0, $s0 + nop + nop + nop + +#ifdef CONFIG_SMP + ;; Read CPU ID + move 0, $srs + nop + nop + nop + move $s10, $r0 + cmpq 0, $r0 + beq master_cpu + nop +slave_cpu: + ; A slave waits for cpu_now_booting to be equal to CPU ID. + move.d cpu_now_booting, $r1 +slave_wait: + cmp.d [$r1], $r0 + bne slave_wait + nop + ; Time to boot-up. Get stack location provided by master CPU. + move.d smp_init_current_idle_thread, $r1 + move.d [$r1], $sp + add.d 8192, $sp + move.d ebp_start, $r0 ; Defined in linker-script. + move $r0, $ebp + jsr smp_callin + nop +master_cpu: +#endif +#ifndef CONFIG_ETRAXFS_SIM + ;; Check if starting from DRAM or flash. + lapcq ., $r0 + and.d 0x7fffffff, $r0 ; Mask off the non-cache bit. + cmp.d 0x10000, $r0 ; Arbitrary, something above this code. + blo _inflash0 + nop +#endif + + jump _inram ; Jump to cached RAM. + nop + + ;; Jumpgate. +_inflash0: + jump _inflash + nop + + ;; Put the following in a section so that storage for it can be + ;; reclaimed after init is finished. + .section ".init.text", "ax" + +_inflash: + + ;; Initialize DRAM. + cmp.d RAM_INIT_MAGIC, $r8 ; Already initialized? + beq _dram_initialized + nop + +#include "../lib/dram_init.S" + +_dram_initialized: + ;; Copy the text and data section to DRAM. This depends on that the + ;; variables used below are correctly set up by the linker script. + ;; The calculated value stored in R4 is used below. + moveq 0, $r0 ; Source. + move.d text_start, $r1 ; Destination. + move.d __vmlinux_end, $r2 + move.d $r2, $r4 + sub.d $r1, $r4 +1: move.w [$r0+], $r3 + move.w $r3, [$r1+] + cmp.d $r2, $r1 + blo 1b + nop + + ;; Keep CRAMFS in flash. + moveq 0, $r0 + move.d romfs_length, $r1 + move.d $r0, [$r1] + move.d [$r4], $r0 ; cramfs_super.magic + cmp.d CRAMFS_MAGIC, $r0 + bne 1f + nop + + addoq +4, $r4, $acr + move.d [$acr], $r0 + move.d romfs_length, $r1 + move.d $r0, [$r1] + add.d 0xf0000000, $r4 ; Add cached flash start in virtual memory. + move.d romfs_start, $r1 + move.d $r4, [$r1] +1: moveq 1, $r0 + move.d romfs_in_flash, $r1 + move.d $r0, [$r1] + + jump _start_it ; Jump to cached code. + nop + +_inram: + ;; Check if booting from NAND flash (in that case we just remember the offset + ;; into the flash where cramfs should be). + move.d REG_ADDR(config, regi_config, r_bootsel), $r0 + move.d [$r0], $r0 + and.d REG_MASK(config, r_bootsel, boot_mode), $r0 + cmp.d REG_STATE(config, r_bootsel, boot_mode, nand), $r0 + bne move_cramfs + moveq 1,$r0 + move.d crisv32_nand_boot, $r1 + move.d $r0, [$r1] + move.d crisv32_nand_cramfs_offset, $r1 + move.d $r9, [$r1] + moveq 1, $r0 + move.d romfs_in_flash, $r1 + move.d $r0, [$r1] + jump _start_it + nop + +move_cramfs: + ;; Move the cramfs after BSS. + moveq 0, $r0 + move.d romfs_length, $r1 + move.d $r0, [$r1] + +#ifndef CONFIG_ETRAXFS_SIM + ;; The kernel could have been unpacked to DRAM by the loader, but + ;; the cramfs image could still be inte the flash immediately + ;; following the compressed kernel image. The loaded passes the address + ;; of the bute succeeding the last compressed byte in the flash in + ;; register R9 when starting the kernel. + cmp.d 0x0ffffff8, $r9 + bhs _no_romfs_in_flash ; R9 points outside the flash area. + nop +#else + ba _no_romfs_in_flash + nop +#endif + move.d [$r9], $r0 ; cramfs_super.magic + cmp.d CRAMFS_MAGIC, $r0 + bne _no_romfs_in_flash + nop + + addoq +4, $r9, $acr + move.d [$acr], $r0 + move.d romfs_length, $r1 + move.d $r0, [$r1] + add.d 0xf0000000, $r9 ; Add cached flash start in virtual memory. + move.d romfs_start, $r1 + move.d $r9, [$r1] + moveq 1, $r0 + move.d romfs_in_flash, $r1 + move.d $r0, [$r1] + + jump _start_it ; Jump to cached code. + nop + +_no_romfs_in_flash: + ;; Look for cramfs. +#ifndef CONFIG_ETRAXFS_SIM + move.d __vmlinux_end, $r0 +#else + move.d __end, $r0 +#endif + move.d [$r0], $r1 + cmp.d CRAMFS_MAGIC, $r1 + bne 2f + nop + + addoq +4, $r0, $acr + move.d [$acr], $r2 + move.d _end, $r1 + move.d romfs_start, $r3 + move.d $r1, [$r3] + move.d romfs_length, $r3 + move.d $r2, [$r3] + +#ifndef CONFIG_ETRAXFS_SIM + add.d $r2, $r0 + add.d $r2, $r1 + + lsrq 1, $r2 ; Size is in bytes, we copy words. + addq 1, $r2 +1: + move.w [$r0], $r3 + move.w $r3, [$r1] + subq 2, $r0 + subq 2, $r1 + subq 1, $r2 + bne 1b + nop +#endif + +2: + moveq 0, $r0 + move.d romfs_in_flash, $r1 + move.d $r0, [$r1] + + jump _start_it ; Jump to cached code. + nop + +_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 contains a task structure for each task. This + ;; the initial kernel stack is in the same page as the init_task, + ;; but starts at the top of the page, i.e. + 8192 bytes. + move.d init_thread_union + 8192, $sp + move.d ebp_start, $r0 ; Defined in linker-script. + move $r0, $ebp + move.d etrax_irv, $r1 ; Set the exception base register and pointer. + move.d $r0, [$r1] + +#ifndef CONFIG_ETRAXFS_SIM + ;; Clear the 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 +#endif + +#ifdef CONFIG_ETRAXFS_SIM + /* Set the watchdog timeout to something big. Will be removed when */ + /* watchdog can be disabled with command line option */ + move.d 0x7fffffff, $r10 + jsr CPU_WATCHDOG_TIMEOUT + nop +#endif + + ; Initialize registers to increase determinism + move.d __bss_start, $r0 + movem [$r0], $r13 + + jump start_kernel ; Jump to start_kernel() in init/main.c. + nop + + .data +etrax_irv: + .dword 0 +romfs_start: + .dword 0 +romfs_length: + .dword 0 +romfs_in_flash: + .dword 0 +crisv32_nand_boot: + .dword 0 +crisv32_nand_cramfs_offset: + .dword 0 + +swapper_pg_dir = 0xc0002000 + + .section ".init.data", "aw" + +#include "../lib/hw_settings.S" diff --git a/arch/cris/arch-v32/kernel/io.c b/arch/cris/arch-v32/kernel/io.c new file mode 100644 index 0000000..6bc9f26 --- /dev/null +++ b/arch/cris/arch-v32/kernel/io.c @@ -0,0 +1,154 @@ +/* + * Helper functions for I/O pins. + * + * Copyright (c) 2004 Axis Communications AB. + */ + +#include <linux/config.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <asm/io.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/hwregs/gio_defs.h> + +struct crisv32_ioport crisv32_ioports[] = +{ + { + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pa_oe), + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pa_dout), + (unsigned long*)REG_ADDR(gio, regi_gio, r_pa_din), + 8 + }, + { + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pb_oe), + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pb_dout), + (unsigned long*)REG_ADDR(gio, regi_gio, r_pb_din), + 18 + }, + { + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pc_oe), + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pc_dout), + (unsigned long*)REG_ADDR(gio, regi_gio, r_pc_din), + 18 + }, + { + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pd_oe), + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pd_dout), + (unsigned long*)REG_ADDR(gio, regi_gio, r_pd_din), + 18 + }, + { + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pe_oe), + (unsigned long*)REG_ADDR(gio, regi_gio, rw_pe_dout), + (unsigned long*)REG_ADDR(gio, regi_gio, r_pe_din), + 18 + } +}; + +#define NBR_OF_PORTS sizeof(crisv32_ioports)/sizeof(struct crisv32_ioport) + +struct crisv32_iopin crisv32_led1_green; +struct crisv32_iopin crisv32_led1_red; +struct crisv32_iopin crisv32_led2_green; +struct crisv32_iopin crisv32_led2_red; +struct crisv32_iopin crisv32_led3_green; +struct crisv32_iopin crisv32_led3_red; + +/* Dummy port used when green LED and red LED is on the same bit */ +static unsigned long io_dummy; +static struct crisv32_ioport dummy_port = +{ + &io_dummy, + &io_dummy, + &io_dummy, + 18 +}; +static struct crisv32_iopin dummy_led = +{ + &dummy_port, + 0 +}; + +static int __init crisv32_io_init(void) +{ + int ret = 0; + /* Initialize LEDs */ + ret += crisv32_io_get_name(&crisv32_led1_green, CONFIG_ETRAX_LED1G); + ret += crisv32_io_get_name(&crisv32_led1_red, CONFIG_ETRAX_LED1R); + ret += crisv32_io_get_name(&crisv32_led2_green, CONFIG_ETRAX_LED2G); + ret += crisv32_io_get_name(&crisv32_led2_red, CONFIG_ETRAX_LED2R); + ret += crisv32_io_get_name(&crisv32_led3_green, CONFIG_ETRAX_LED3G); + ret += crisv32_io_get_name(&crisv32_led3_red, CONFIG_ETRAX_LED3R); + crisv32_io_set_dir(&crisv32_led1_green, crisv32_io_dir_out); + crisv32_io_set_dir(&crisv32_led1_red, crisv32_io_dir_out); + crisv32_io_set_dir(&crisv32_led2_green, crisv32_io_dir_out); + crisv32_io_set_dir(&crisv32_led2_red, crisv32_io_dir_out); + crisv32_io_set_dir(&crisv32_led3_green, crisv32_io_dir_out); + crisv32_io_set_dir(&crisv32_led3_red, crisv32_io_dir_out); + + if (!strcmp(CONFIG_ETRAX_LED1G, CONFIG_ETRAX_LED1R)) + crisv32_led1_red = dummy_led; + if (!strcmp(CONFIG_ETRAX_LED2G, CONFIG_ETRAX_LED2R)) + crisv32_led2_red = dummy_led; + + return ret; +} + +__initcall(crisv32_io_init); + +int crisv32_io_get(struct crisv32_iopin* iopin, + unsigned int port, unsigned int pin) +{ + if (port > NBR_OF_PORTS) + return -EINVAL; + if (port > crisv32_ioports[port].pin_count) + return -EINVAL; + + iopin->bit = 1 << pin; + iopin->port = &crisv32_ioports[port]; + + if (crisv32_pinmux_alloc(port, pin, pin, pinmux_gpio)) + return -EIO; + + return 0; +} + +int crisv32_io_get_name(struct crisv32_iopin* iopin, + char* name) +{ + int port; + int pin; + + if (toupper(*name) == 'P') + name++; + + if (toupper(*name) < 'A' || toupper(*name) > 'E') + return -EINVAL; + + port = toupper(*name) - 'A'; + name++; + pin = simple_strtoul(name, NULL, 10); + + if (pin < 0 || pin > crisv32_ioports[port].pin_count) + return -EINVAL; + + iopin->bit = 1 << pin; + iopin->port = &crisv32_ioports[port]; + + if (crisv32_pinmux_alloc(port, pin, pin, pinmux_gpio)) + return -EIO; + + return 0; +} + +#ifdef CONFIG_PCI +/* PCI I/O access stuff */ +struct cris_io_operations* cris_iops = NULL; +EXPORT_SYMBOL(cris_iops); +#endif + diff --git a/arch/cris/arch-v32/kernel/irq.c b/arch/cris/arch-v32/kernel/irq.c new file mode 100644 index 0000000..c78cc26 --- /dev/null +++ b/arch/cris/arch-v32/kernel/irq.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2003, Axis Communications AB. + */ + +#include <asm/irq.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/smp.h> +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/profile.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/threads.h> +#include <linux/spinlock.h> +#include <linux/kernel_stat.h> +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/intr_vect.h> +#include <asm/arch/hwregs/intr_vect_defs.h> + +#define CPU_FIXED -1 + +/* IRQ masks (refer to comment for crisv32_do_multiple) */ +#define TIMER_MASK (1 << (TIMER_INTR_VECT - FIRST_IRQ)) +#ifdef CONFIG_ETRAX_KGDB +#if defined(CONFIG_ETRAX_KGDB_PORT0) +#define IGNOREMASK (1 << (SER0_INTR_VECT - FIRST_IRQ)) +#elif defined(CONFIG_ETRAX_KGDB_PORT1) +#define IGNOREMASK (1 << (SER1_INTR_VECT - FIRST_IRQ)) +#elif defined(CONFIG_ETRAX_KGB_PORT2) +#define IGNOREMASK (1 << (SER2_INTR_VECT - FIRST_IRQ)) +#elif defined(CONFIG_ETRAX_KGDB_PORT3) +#define IGNOREMASK (1 << (SER3_INTR_VECT - FIRST_IRQ)) +#endif +#endif + +DEFINE_SPINLOCK(irq_lock); + +struct cris_irq_allocation +{ + int cpu; /* The CPU to which the IRQ is currently allocated. */ + cpumask_t mask; /* The CPUs to which the IRQ may be allocated. */ +}; + +struct cris_irq_allocation irq_allocations[NR_IRQS] = + {[0 ... NR_IRQS - 1] = {0, CPU_MASK_ALL}}; + +static unsigned long irq_regs[NR_CPUS] = +{ + regi_irq, +#ifdef CONFIG_SMP + regi_irq2, +#endif +}; + +unsigned long cpu_irq_counters[NR_CPUS]; +unsigned long irq_counters[NR_REAL_IRQS]; + +/* From irq.c. */ +extern void weird_irq(void); + +/* From entry.S. */ +extern void system_call(void); +extern void nmi_interrupt(void); +extern void multiple_interrupt(void); +extern void gdb_handle_exception(void); +extern void i_mmu_refill(void); +extern void i_mmu_invalid(void); +extern void i_mmu_access(void); +extern void i_mmu_execute(void); +extern void d_mmu_refill(void); +extern void d_mmu_invalid(void); +extern void d_mmu_access(void); +extern void d_mmu_write(void); + +/* From kgdb.c. */ +extern void kgdb_init(void); +extern void breakpoint(void); + +/* + * Build the IRQ handler stubs using macros from irq.h. First argument is the + * IRQ number, the second argument is the corresponding bit in + * intr_rw_vect_mask found in asm/arch/hwregs/intr_vect_defs.h. + */ +BUILD_IRQ(0x31, (1 << 0)) /* memarb */ +BUILD_IRQ(0x32, (1 << 1)) /* gen_io */ +BUILD_IRQ(0x33, (1 << 2)) /* iop0 */ +BUILD_IRQ(0x34, (1 << 3)) /* iop1 */ +BUILD_IRQ(0x35, (1 << 4)) /* iop2 */ +BUILD_IRQ(0x36, (1 << 5)) /* iop3 */ +BUILD_IRQ(0x37, (1 << 6)) /* dma0 */ +BUILD_IRQ(0x38, (1 << 7)) /* dma1 */ +BUILD_IRQ(0x39, (1 << 8)) /* dma2 */ +BUILD_IRQ(0x3a, (1 << 9)) /* dma3 */ +BUILD_IRQ(0x3b, (1 << 10)) /* dma4 */ +BUILD_IRQ(0x3c, (1 << 11)) /* dma5 */ +BUILD_IRQ(0x3d, (1 << 12)) /* dma6 */ +BUILD_IRQ(0x3e, (1 << 13)) /* dma7 */ +BUILD_IRQ(0x3f, (1 << 14)) /* dma8 */ +BUILD_IRQ(0x40, (1 << 15)) /* dma9 */ +BUILD_IRQ(0x41, (1 << 16)) /* ata */ +BUILD_IRQ(0x42, (1 << 17)) /* sser0 */ +BUILD_IRQ(0x43, (1 << 18)) /* sser1 */ +BUILD_IRQ(0x44, (1 << 19)) /* ser0 */ +BUILD_IRQ(0x45, (1 << 20)) /* ser1 */ +BUILD_IRQ(0x46, (1 << 21)) /* ser2 */ +BUILD_IRQ(0x47, (1 << 22)) /* ser3 */ +BUILD_IRQ(0x48, (1 << 23)) +BUILD_IRQ(0x49, (1 << 24)) /* eth0 */ +BUILD_IRQ(0x4a, (1 << 25)) /* eth1 */ +BUILD_TIMER_IRQ(0x4b, (1 << 26))/* timer */ +BUILD_IRQ(0x4c, (1 << 27)) /* bif_arb */ +BUILD_IRQ(0x4d, (1 << 28)) /* bif_dma */ +BUILD_IRQ(0x4e, (1 << 29)) /* ext */ +BUILD_IRQ(0x4f, (1 << 29)) /* ipi */ + +/* Pointers to the low-level handlers. */ +static void (*interrupt[NR_IRQS])(void) = { + IRQ0x31_interrupt, IRQ0x32_interrupt, IRQ0x33_interrupt, + IRQ0x34_interrupt, IRQ0x35_interrupt, IRQ0x36_interrupt, + IRQ0x37_interrupt, IRQ0x38_interrupt, IRQ0x39_interrupt, + IRQ0x3a_interrupt, IRQ0x3b_interrupt, IRQ0x3c_interrupt, + IRQ0x3d_interrupt, IRQ0x3e_interrupt, IRQ0x3f_interrupt, + IRQ0x40_interrupt, IRQ0x41_interrupt, IRQ0x42_interrupt, + IRQ0x43_interrupt, IRQ0x44_interrupt, IRQ0x45_interrupt, + IRQ0x46_interrupt, IRQ0x47_interrupt, IRQ0x48_interrupt, + IRQ0x49_interrupt, IRQ0x4a_interrupt, IRQ0x4b_interrupt, + IRQ0x4c_interrupt, IRQ0x4d_interrupt, IRQ0x4e_interrupt, + IRQ0x4f_interrupt +}; + +void +block_irq(int irq, int cpu) +{ + int intr_mask; + unsigned long flags; + + spin_lock_irqsave(&irq_lock, flags); + intr_mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask); + + /* Remember; 1 let thru, 0 block. */ + intr_mask &= ~(1 << (irq - FIRST_IRQ)); + + REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, intr_mask); + spin_unlock_irqrestore(&irq_lock, flags); +} + +void +unblock_irq(int irq, int cpu) +{ + int intr_mask; + unsigned long flags; + + spin_lock_irqsave(&irq_lock, flags); + intr_mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask); + + /* Remember; 1 let thru, 0 block. */ + intr_mask |= (1 << (irq - FIRST_IRQ)); + + REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, intr_mask); + spin_unlock_irqrestore(&irq_lock, flags); +} + +/* Find out which CPU the irq should be allocated to. */ +static int irq_cpu(int irq) +{ + int cpu; + unsigned long flags; + + spin_lock_irqsave(&irq_lock, flags); + cpu = irq_allocations[irq - FIRST_IRQ].cpu; + + /* Fixed interrupts stay on the local CPU. */ + if (cpu == CPU_FIXED) + { + spin_unlock_irqrestore(&irq_lock, flags); + return smp_processor_id(); + } + + + /* Let the interrupt stay if possible */ + if (cpu_isset(cpu, irq_allocations[irq - FIRST_IRQ].mask)) + goto out; + + /* IRQ must be moved to another CPU. */ + cpu = first_cpu(irq_allocations[irq - FIRST_IRQ].mask); + irq_allocations[irq - FIRST_IRQ].cpu = cpu; +out: + spin_unlock_irqrestore(&irq_lock, flags); + return cpu; +} + +void +mask_irq(int irq) +{ + int cpu; + + for (cpu = 0; cpu < NR_CPUS; cpu++) + block_irq(irq, cpu); +} + +void +unmask_irq(int irq) +{ + unblock_irq(irq, irq_cpu(irq)); +} + + +static unsigned int startup_crisv32_irq(unsigned int irq) +{ + unmask_irq(irq); + return 0; +} + +static void shutdown_crisv32_irq(unsigned int irq) +{ + mask_irq(irq); +} + +static void enable_crisv32_irq(unsigned int irq) +{ + unmask_irq(irq); +} + +static void disable_crisv32_irq(unsigned int irq) +{ + mask_irq(irq); +} + +static void ack_crisv32_irq(unsigned int irq) +{ +} + +static void end_crisv32_irq(unsigned int irq) +{ +} + +void set_affinity_crisv32_irq(unsigned int irq, cpumask_t dest) +{ + unsigned long flags; + spin_lock_irqsave(&irq_lock, flags); + irq_allocations[irq - FIRST_IRQ].mask = dest; + spin_unlock_irqrestore(&irq_lock, flags); +} + +static struct hw_interrupt_type crisv32_irq_type = { + .typename = "CRISv32", + .startup = startup_crisv32_irq, + .shutdown = shutdown_crisv32_irq, + .enable = enable_crisv32_irq, + .disable = disable_crisv32_irq, + .ack = ack_crisv32_irq, + .end = end_crisv32_irq, + .set_affinity = set_affinity_crisv32_irq +}; + +void +set_exception_vector(int n, irqvectptr addr) +{ + etrax_irv->v[n] = (irqvectptr) addr; +} + +extern void do_IRQ(int irq, struct pt_regs * regs); + +void +crisv32_do_IRQ(int irq, int block, struct pt_regs* regs) +{ + /* Interrupts that may not be moved to another CPU and + * are SA_INTERRUPT may skip blocking. This is currently + * only valid for the timer IRQ and the IPI and is used + * for the timer interrupt to avoid watchdog starvation. + */ + if (!block) { + do_IRQ(irq, regs); + return; + } + + block_irq(irq, smp_processor_id()); + do_IRQ(irq, regs); + + unblock_irq(irq, irq_cpu(irq)); +} + +/* If multiple interrupts occur simultaneously we get a multiple + * interrupt from the CPU and software has to sort out which + * interrupts that happened. There are two special cases here: + * + * 1. Timer interrupts may never be blocked because of the + * watchdog (refer to comment in include/asr/arch/irq.h) + * 2. GDB serial port IRQs are unhandled here and will be handled + * as a single IRQ when it strikes again because the GDB + * stubb wants to save the registers in its own fashion. + */ +void +crisv32_do_multiple(struct pt_regs* regs) +{ + int cpu; + int mask; + int masked; + int bit; + + cpu = smp_processor_id(); + + /* An extra irq_enter here to prevent softIRQs to run after + * each do_IRQ. This will decrease the interrupt latency. + */ + irq_enter(); + + /* Get which IRQs that happend. */ + masked = REG_RD_INT(intr_vect, irq_regs[cpu], r_masked_vect); + + /* Calculate new IRQ mask with these IRQs disabled. */ + mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask); + mask &= ~masked; + + /* Timer IRQ is never masked */ + if (masked & TIMER_MASK) + mask |= TIMER_MASK; + + /* Block all the IRQs */ + REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, mask); + + /* Check for timer IRQ and handle it special. */ + if (masked & TIMER_MASK) { + masked &= ~TIMER_MASK; + do_IRQ(TIMER_INTR_VECT, regs); + } + +#ifdef IGNORE_MASK + /* Remove IRQs that can't be handled as multiple. */ + masked &= ~IGNORE_MASK; +#endif + + /* Handle the rest of the IRQs. */ + for (bit = 0; bit < 32; bit++) + { + if (masked & (1 << bit)) + do_IRQ(bit + FIRST_IRQ, regs); + } + + /* Unblock all the IRQs. */ + mask = REG_RD_INT(intr_vect, irq_regs[cpu], rw_mask); + mask |= masked; + REG_WR_INT(intr_vect, irq_regs[cpu], rw_mask, mask); + + /* This irq_exit() will trigger the soft IRQs. */ + irq_exit(); +} + +/* + * This is called by start_kernel. It fixes the IRQ masks and setup the + * interrupt vector table to point to bad_interrupt pointers. + */ +void __init +init_IRQ(void) +{ + int i; + int j; + reg_intr_vect_rw_mask vect_mask = {0}; + + /* Clear all interrupts masks. */ + REG_WR(intr_vect, regi_irq, rw_mask, vect_mask); + + for (i = 0; i < 256; i++) + etrax_irv->v[i] = weird_irq; + + /* Point all IRQ's to bad handlers. */ + for (i = FIRST_IRQ, j = 0; j < NR_IRQS; i++, j++) { + irq_desc[j].handler = &crisv32_irq_type; + set_exception_vector(i, interrupt[j]); + } + + /* Mark Timer and IPI IRQs as CPU local */ + irq_allocations[TIMER_INTR_VECT - FIRST_IRQ].cpu = CPU_FIXED; + irq_desc[TIMER_INTR_VECT].status |= IRQ_PER_CPU; + irq_allocations[IPI_INTR_VECT - FIRST_IRQ].cpu = CPU_FIXED; + irq_desc[IPI_INTR_VECT].status |= IRQ_PER_CPU; + + set_exception_vector(0x00, nmi_interrupt); + set_exception_vector(0x30, multiple_interrupt); + + /* Set up handler for various MMU bus faults. */ + set_exception_vector(0x04, i_mmu_refill); + set_exception_vector(0x05, i_mmu_invalid); + set_exception_vector(0x06, i_mmu_access); + set_exception_vector(0x07, i_mmu_execute); + set_exception_vector(0x08, d_mmu_refill); + set_exception_vector(0x09, d_mmu_invalid); + set_exception_vector(0x0a, d_mmu_access); + set_exception_vector(0x0b, d_mmu_write); + + /* The system-call trap is reached by "break 13". */ + set_exception_vector(0x1d, system_call); + + /* Exception handlers for debugging, both user-mode and kernel-mode. */ + + /* Break 8. */ + set_exception_vector(0x18, gdb_handle_exception); + /* Hardware single step. */ + set_exception_vector(0x3, gdb_handle_exception); + /* Hardware breakpoint. */ + set_exception_vector(0xc, gdb_handle_exception); + +#ifdef CONFIG_ETRAX_KGDB + kgdb_init(); + /* Everything is set up; now trap the kernel. */ + breakpoint(); +#endif +} + diff --git a/arch/cris/arch-v32/kernel/kgdb.c b/arch/cris/arch-v32/kernel/kgdb.c new file mode 100644 index 0000000..480e563 --- /dev/null +++ b/arch/cris/arch-v32/kernel/kgdb.c @@ -0,0 +1,1660 @@ +/* + * arch/cris/arch-v32/kernel/kgdb.c + * + * CRIS v32 version by Orjan Friberg, Axis Communications AB. + * + * S390 version + * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation + * Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com), + * + * Originally written by Glenn Engel, Lake Stevens Instrument Division + * + * Contributed by HP Systems + * + * Modified for SPARC by Stu Grossman, Cygnus Support. + * + * Modified for Linux/MIPS (and MIPS in general) by Andreas Busse + * Send complaints, suggestions etc. to <andy@waldorf-gmbh.de> + * + * Copyright (C) 1995 Andreas Busse + */ + +/* FIXME: Check the documentation. */ + +/* + * 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 <asm/irq.h> +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/intr_vect_defs.h> +#include <asm/arch/hwregs/ser_defs.h> + +/* From entry.S. */ +extern void gdb_handle_exception(void); +/* From kgdb_asm.S. */ +extern void kgdb_handle_exception(void); + +static int kgdb_started = 0; + +/********************************* Register image ****************************/ + +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 (if any) */ + unsigned int r9; /* 0x24 */ + unsigned int r10; /* 0x28 */ + unsigned int r11; /* 0x2C */ + unsigned int r12; /* 0x30 */ + unsigned int r13; /* 0x34 */ + unsigned int sp; /* 0x38; R14, Stack pointer */ + unsigned int acr; /* 0x3C; R15, Address calculation register. */ + + unsigned char bz; /* 0x40; P0, 8-bit zero register */ + unsigned char vr; /* 0x41; P1, Version register (8-bit) */ + unsigned int pid; /* 0x42; P2, Process ID */ + unsigned char srs; /* 0x46; P3, Support register select (8-bit) */ + unsigned short wz; /* 0x47; P4, 16-bit zero register */ + unsigned int exs; /* 0x49; P5, Exception status */ + unsigned int eda; /* 0x4D; P6, Exception data address */ + unsigned int mof; /* 0x51; P7, Multiply overflow register */ + unsigned int dz; /* 0x55; P8, 32-bit zero register */ + unsigned int ebp; /* 0x59; P9, Exception base pointer */ + unsigned int erp; /* 0x5D; P10, Exception return pointer. Contains the PC we are interested in. */ + unsigned int srp; /* 0x61; P11, Subroutine return pointer */ + unsigned int nrp; /* 0x65; P12, NMI return pointer */ + unsigned int ccs; /* 0x69; P13, Condition code stack */ + unsigned int usp; /* 0x6D; P14, User mode stack pointer */ + unsigned int spc; /* 0x71; P15, Single step PC */ + unsigned int pc; /* 0x75; Pseudo register (for the most part set to ERP). */ + +} registers; + +typedef +struct bp_register_image +{ + /* Support register bank 0. */ + unsigned int s0_0; + unsigned int s1_0; + unsigned int s2_0; + unsigned int s3_0; + unsigned int s4_0; + unsigned int s5_0; + unsigned int s6_0; + unsigned int s7_0; + unsigned int s8_0; + unsigned int s9_0; + unsigned int s10_0; + unsigned int s11_0; + unsigned int s12_0; + unsigned int s13_0; + unsigned int s14_0; + unsigned int s15_0; + + /* Support register bank 1. */ + unsigned int s0_1; + unsigned int s1_1; + unsigned int s2_1; + unsigned int s3_1; + unsigned int s4_1; + unsigned int s5_1; + unsigned int s6_1; + unsigned int s7_1; + unsigned int s8_1; + unsigned int s9_1; + unsigned int s10_1; + unsigned int s11_1; + unsigned int s12_1; + unsigned int s13_1; + unsigned int s14_1; + unsigned int s15_1; + + /* Support register bank 2. */ + unsigned int s0_2; + unsigned int s1_2; + unsigned int s2_2; + unsigned int s3_2; + unsigned int s4_2; + unsigned int s5_2; + unsigned int s6_2; + unsigned int s7_2; + unsigned int s8_2; + unsigned int s9_2; + unsigned int s10_2; + unsigned int s11_2; + unsigned int s12_2; + unsigned int s13_2; + unsigned int s14_2; + unsigned int s15_2; + + /* Support register bank 3. */ + unsigned int s0_3; /* BP_CTRL */ + unsigned int s1_3; /* BP_I0_START */ + unsigned int s2_3; /* BP_I0_END */ + unsigned int s3_3; /* BP_D0_START */ + unsigned int s4_3; /* BP_D0_END */ + unsigned int s5_3; /* BP_D1_START */ + unsigned int s6_3; /* BP_D1_END */ + unsigned int s7_3; /* BP_D2_START */ + unsigned int s8_3; /* BP_D2_END */ + unsigned int s9_3; /* BP_D3_START */ + unsigned int s10_3; /* BP_D3_END */ + unsigned int s11_3; /* BP_D4_START */ + unsigned int s12_3; /* BP_D4_END */ + unsigned int s13_3; /* BP_D5_START */ + unsigned int s14_3; /* BP_D5_END */ + unsigned int s15_3; /* BP_RESERVED */ + +} support_registers; + +enum register_name +{ + R0, R1, R2, R3, + R4, R5, R6, R7, + R8, R9, R10, R11, + R12, R13, SP, ACR, + + BZ, VR, PID, SRS, + WZ, EXS, EDA, MOF, + DZ, EBP, ERP, SRP, + NRP, CCS, USP, SPC, + PC, + + S0, S1, S2, S3, + S4, S5, S6, S7, + S8, S9, S10, S11, + S12, S13, S14, S15 + +}; + +/* 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, 4, 1, + 2, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + + 4, + + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4, 4, + 4, 4, 4 + +}; + +/* Contains the register image of the kernel. + (Global so that they can be reached from assembler code.) */ +registers reg; +support_registers sreg; + +/************** Prototypes for local library functions ***********************/ + +/* Copy of strcpy from libc. */ +static char *gdb_cris_strcpy(char *s1, const char *s2); + +/* Copy of strlen from libc. */ +static int gdb_cris_strlen(const char *s); + +/* Copy of memchr from libc. */ +static void *gdb_cris_memchr(const void *s, int c, int n); + +/* Copy of strtol from libc. Does only support base 16. */ +static int gdb_cris_strtol(const char *s, char **endptr, int base); + +/********************** Prototypes for local functions. **********************/ + +/* Write a value to a specified register regno in the register image + of the current thread. */ +static int write_register(int regno, char *val); + +/* Read a value from a specified register in the register image. Returns the + status of the read operation. The register value is returned in valptr. */ +static int read_register(char regno, unsigned int *valptr); + +/* Serial port, reads one character. ETRAX 100 specific. from debugport.c */ +int getDebugChar(void); + +#ifdef CONFIG_ETRAXFS_SIM +int getDebugChar(void) +{ + return socketread(); +} +#endif + +/* Serial port, writes one character. ETRAX 100 specific. from debugport.c */ +void putDebugChar(int val); + +#ifdef CONFIG_ETRAXFS_SIM +void putDebugChar(int val) +{ + socketwrite((char *)&val, 1); +} +#endif + +/* Returns the character equivalent of a nibble, bit 7, 6, 5, and 4 of a byte, + represented by int x. */ +static char highhex(int x); + +/* Returns the character equivalent of a nibble, bit 3, 2, 1, and 0 of a byte, + represented by int x. */ +static char lowhex(int x); + +/* Returns the integer equivalent of a hexadecimal character. */ +static int hex(char ch); + +/* 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); + +/* 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); + +/* 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. */ +static unsigned char *bin2mem(unsigned char *mem, unsigned char *buf, int count); + +/* Await the sequence $<data>#<checksum> and store <data> in the array buffer + returned. */ +static void getpacket(char *buffer); + +/* Send $<data>#<checksum> from the <data> in the array buffer. */ +static void putpacket(char *buffer); + +/* Build and send a response packet in order to inform the host the + stub is stopped. */ +static void stub_is_stopped(int sigval); + +/* All expected commands are sent from remote.c. Send a response according + to the description in remote.c. Not static since it needs to be reached + from assembler code. */ +void handle_exception(int sigval); + +/* Performs a complete re-start from scratch. ETRAX specific. */ +static void kill_restart(void); + +/******************** Prototypes for global functions. ***********************/ + +/* The string str is prepended with the GDB printout token and sent. */ +void putDebugString(const unsigned char *str, int len); + +/* A static breakpoint to be used at startup. */ +void breakpoint(void); + +/* Avoid warning as the internal_stack is not used in the C-code. */ +#define USEDVAR(name) { if (name) { ; } } +#define USEDFUN(name) { void (*pf)(void) = (void *)name; USEDVAR(pf) } + +/********************************** Packet I/O ******************************/ +/* BUFMAX defines the maximum number of characters in + inbound/outbound buffers */ +/* FIXME: How do we know it's enough? */ +#define BUFMAX 512 + +/* Run-length encoding maximum length. Send 64 at most. */ +#define RUNLENMAX 64 + +/* Definition of all valid hexadecimal characters */ +static const char hexchars[] = "0123456789abcdef"; + +/* The inbound/outbound buffers used in packet I/O */ +static char input_buffer[BUFMAX]; +static char output_buffer[BUFMAX]; + +/* Error and warning messages. */ +enum error_type +{ + SUCCESS, E01, E02, E03, E04, E05, E06, +}; + +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.", +}; + +/********************************** Breakpoint *******************************/ +/* Use an internal stack in the breakpoint and interrupt response routines. + FIXME: How do we know the size of this stack is enough? + Global so it can be reached from assembler code. */ +#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 + ERP as it is whereas a dynamic breakpoint requires subtraction with 2 + in order to execute the instruction. The first breakpoint is static; all + following are assumed to be dynamic. */ +static int dynamic_bp = 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(hexchars, *s1, base)) != NULL; ++s1) + x = x * base + (sd - hexchars); + + if (endptr) { + /* Unconverted suffix is stored in endptr unless endptr is NULL. */ + *endptr = s1; + } + + return x; +} + +/********************************* 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; + + if (regno >= R0 && regno <= ACR) { + /* Consecutive 32-bit registers. */ + hex2mem((unsigned char *)®.r0 + (regno - R0) * sizeof(unsigned int), + val, sizeof(unsigned int)); + + } else if (regno == BZ || regno == VR || regno == WZ || regno == DZ) { + /* Read-only registers. */ + status = E02; + + } else if (regno == PID) { + /* 32-bit register. (Even though we already checked SRS and WZ, we cannot + combine this with the EXS - SPC write since SRS and WZ have different size.) */ + hex2mem((unsigned char *)®.pid, val, sizeof(unsigned int)); + + } else if (regno == SRS) { + /* 8-bit register. */ + hex2mem((unsigned char *)®.srs, val, sizeof(unsigned char)); + + } else if (regno >= EXS && regno <= SPC) { + /* Consecutive 32-bit registers. */ + hex2mem((unsigned char *)®.exs + (regno - EXS) * sizeof(unsigned int), + val, sizeof(unsigned int)); + + } else if (regno == PC) { + /* Pseudo-register. Treat as read-only. */ + status = E02; + + } else if (regno >= S0 && regno <= S15) { + /* 32-bit registers. */ + hex2mem((unsigned char *)&sreg.s0_0 + (reg.srs * 16 * sizeof(unsigned int)) + (regno - S0) * sizeof(unsigned int), val, sizeof(unsigned int)); + } else { + /* Non-existing register. */ + 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. */ +static int +read_register(char regno, unsigned int *valptr) +{ + int status = SUCCESS; + + /* We read the zero registers from the register struct (instead of just returning 0) + to catch errors. */ + + if (regno >= R0 && regno <= ACR) { + /* Consecutive 32-bit registers. */ + *valptr = *(unsigned int *)((char *)®.r0 + (regno - R0) * sizeof(unsigned int)); + + } else if (regno == BZ || regno == VR) { + /* Consecutive 8-bit registers. */ + *valptr = (unsigned int)(*(unsigned char *) + ((char *)®.bz + (regno - BZ) * sizeof(char))); + + } else if (regno == PID) { + /* 32-bit register. */ + *valptr = *(unsigned int *)((char *)®.pid); + + } else if (regno == SRS) { + /* 8-bit register. */ + *valptr = (unsigned int)(*(unsigned char *)((char *)®.srs)); + + } else if (regno == WZ) { + /* 16-bit register. */ + *valptr = (unsigned int)(*(unsigned short *)(char *)®.wz); + + } else if (regno >= EXS && regno <= PC) { + /* Consecutive 32-bit registers. */ + *valptr = *(unsigned int *)((char *)®.exs + (regno - EXS) * sizeof(unsigned int)); + + } else if (regno >= S0 && regno <= S15) { + /* Consecutive 32-bit registers, located elsewhere. */ + *valptr = *(unsigned int *)((char *)&sreg.s0_0 + (reg.srs * 16 * sizeof(unsigned int)) + (regno - S0) * sizeof(unsigned int)); + + } else { + /* Non-existing register. */ + status = E05; + } + return status; + +} + +/********************************** Packet I/O ******************************/ +/* Returns the character equivalent of a nibble, bit 7, 6, 5, and 4 of a byte, + represented by int x. */ +static inline char +highhex(int x) +{ + return hexchars[(x >> 4) & 0xf]; +} + +/* Returns the character equivalent of a nibble, bit 3, 2, 1, and 0 of a byte, + represented by int x. */ +static inline char +lowhex(int x) +{ + return hexchars[x & 0xf]; +} + +/* 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) { + /* Invalid address, caught by 'm' packet handler. */ + for (i = 0; i < count; i++) { + *buf++ = '0'; + *buf++ = '0'; + } + } else { + /* Valid mem address. */ + for (i = 0; i < count; i++) { + ch = *mem++; + *buf++ = highhex (ch); + *buf++ = lowhex (ch); + } + } + /* Terminate properly. */ + *buf = '\0'; + return buf; +} + +/* Same as mem2hex, but puts it in network byte order. */ +static char * +mem2hex_nbo(char *buf, unsigned char *mem, int count) +{ + int i; + int ch; + + mem += count - 1; + for (i = 0; i < count; i++) { + ch = *mem--; + *buf++ = highhex (ch); + *buf++ = lowhex (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) { + ch = getDebugChar(); + if (ch == '#') + break; + checksum = checksum + ch; + buffer[count] = ch; + count = count + 1; + } + + if (count >= BUFMAX) + continue; + + 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(highhex (checksum)); + putDebugChar(lowhex (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 len) +{ + /* Move SPC forward if we are single-stepping. */ + asm("spchere:"); + asm("move $spc, $r10"); + asm("cmp.d spchere, $r10"); + asm("bne nosstep"); + asm("nop"); + asm("move.d spccont, $r10"); + asm("move $r10, $spc"); + asm("nosstep:"); + + output_buffer[0] = 'O'; + mem2hex(&output_buffer[1], (unsigned char *)str, len); + putpacket(output_buffer); + + asm("spccont:"); +} + +/********************************** 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 = output_buffer; + unsigned int reg_cont; + + /* Send trap type (converted to signal) */ + + *ptr++ = 'T'; + *ptr++ = highhex(sigval); + *ptr++ = lowhex(sigval); + + if (((reg.exs & 0xff00) >> 8) == 0xc) { + + /* Some kind of hardware watchpoint triggered. Find which one + and determine its type (read/write/access). */ + int S, bp, trig_bits = 0, rw_bits = 0; + int trig_mask = 0; + unsigned int *bp_d_regs = &sreg.s3_3; + /* In a lot of cases, the stopped data address will simply be EDA. + In some cases, we adjust it to match the watched data range. + (We don't want to change the actual EDA though). */ + unsigned int stopped_data_address; + /* The S field of EXS. */ + S = (reg.exs & 0xffff0000) >> 16; + + if (S & 1) { + /* Instruction watchpoint. */ + /* FIXME: Check against, and possibly adjust reported EDA. */ + } else { + /* Data watchpoint. Find the one that triggered. */ + for (bp = 0; bp < 6; bp++) { + + /* Dx_RD, Dx_WR in the S field of EXS for this BP. */ + int bitpos_trig = 1 + bp * 2; + /* Dx_BPRD, Dx_BPWR in BP_CTRL for this BP. */ + int bitpos_config = 2 + bp * 4; + + /* Get read/write trig bits for this BP. */ + trig_bits = (S & (3 << bitpos_trig)) >> bitpos_trig; + + /* Read/write config bits for this BP. */ + rw_bits = (sreg.s0_3 & (3 << bitpos_config)) >> bitpos_config; + if (trig_bits) { + /* Sanity check: the BP shouldn't trigger for accesses + that it isn't configured for. */ + if ((rw_bits == 0x1 && trig_bits != 0x1) || + (rw_bits == 0x2 && trig_bits != 0x2)) + panic("Invalid r/w trigging for this BP"); + + /* Mark this BP as trigged for future reference. */ + trig_mask |= (1 << bp); + + if (reg.eda >= bp_d_regs[bp * 2] && + reg.eda <= bp_d_regs[bp * 2 + 1]) { + /* EDA withing range for this BP; it must be the one + we're looking for. */ + stopped_data_address = reg.eda; + break; + } + } + } + if (bp < 6) { + /* Found a trigged BP with EDA within its configured data range. */ + } else if (trig_mask) { + /* Something triggered, but EDA doesn't match any BP's range. */ + for (bp = 0; bp < 6; bp++) { + /* Dx_BPRD, Dx_BPWR in BP_CTRL for this BP. */ + int bitpos_config = 2 + bp * 4; + + /* Read/write config bits for this BP (needed later). */ + rw_bits = (sreg.s0_3 & (3 << bitpos_config)) >> bitpos_config; + + if (trig_mask & (1 << bp)) { + /* EDA within 31 bytes of the configured start address? */ + if (reg.eda + 31 >= bp_d_regs[bp * 2]) { + /* Changing the reported address to match + the start address of the first applicable BP. */ + stopped_data_address = bp_d_regs[bp * 2]; + break; + } else { + /* We continue since we might find another useful BP. */ + printk("EDA doesn't match trigged BP's range"); + } + } + } + } + + /* No match yet? */ + BUG_ON(bp >= 6); + /* Note that we report the type according to what the BP is configured + for (otherwise we'd never report an 'awatch'), not according to how + it trigged. We did check that the trigged bits match what the BP is + configured for though. */ + if (rw_bits == 0x1) { + /* read */ + strncpy(ptr, "rwatch", 6); + ptr += 6; + } else if (rw_bits == 0x2) { + /* write */ + strncpy(ptr, "watch", 5); + ptr += 5; + } else if (rw_bits == 0x3) { + /* access */ + strncpy(ptr, "awatch", 6); + ptr += 6; + } else { + panic("Invalid r/w bits for this BP."); + } + + *ptr++ = ':'; + /* Note that we don't read_register(EDA, ...) */ + ptr = mem2hex_nbo(ptr, (unsigned char *)&stopped_data_address, register_size[EDA]); + *ptr++ = ';'; + } + } + /* Only send PC, frame and stack pointer. */ + read_register(PC, ®_cont); + *ptr++ = highhex(PC); + *ptr++ = lowhex(PC); + *ptr++ = ':'; + ptr = mem2hex(ptr, (unsigned char *)®_cont, register_size[PC]); + *ptr++ = ';'; + + read_register(R8, ®_cont); + *ptr++ = highhex(R8); + *ptr++ = lowhex(R8); + *ptr++ = ':'; + ptr = mem2hex(ptr, (unsigned char *)®_cont, register_size[R8]); + *ptr++ = ';'; + + read_register(SP, ®_cont); + *ptr++ = highhex(SP); + *ptr++ = lowhex(SP); + *ptr++ = ':'; + ptr = mem2hex(ptr, (unsigned char *)®_cont, register_size[SP]); + *ptr++ = ';'; + + /* Send ERP as well; this will save us an entire register fetch in some cases. */ + read_register(ERP, ®_cont); + *ptr++ = highhex(ERP); + *ptr++ = lowhex(ERP); + *ptr++ = ':'; + ptr = mem2hex(ptr, (unsigned char *)®_cont, register_size[ERP]); + *ptr++ = ';'; + + /* null-terminate and send it off */ + *ptr = 0; + putpacket(output_buffer); +} + +/* Returns the size of an instruction that has a delay slot. */ + +int insn_size(unsigned long pc) +{ + unsigned short opcode = *(unsigned short *)pc; + int size = 0; + + switch ((opcode & 0x0f00) >> 8) { + case 0x0: + case 0x9: + case 0xb: + size = 2; + break; + case 0xe: + case 0xf: + size = 6; + break; + case 0xd: + /* Could be 4 or 6; check more bits. */ + if ((opcode & 0xff) == 0xff) + size = 4; + else + size = 6; + break; + default: + panic("Couldn't find size of opcode 0x%x at 0x%lx\n", opcode, pc); + } + + return size; +} + +void register_fixup(int sigval) +{ + /* Compensate for ACR push at the beginning of exception handler. */ + reg.sp += 4; + + /* Standard case. */ + reg.pc = reg.erp; + if (reg.erp & 0x1) { + /* Delay slot bit set. Report as stopped on proper instruction. */ + if (reg.spc) { + /* Rely on SPC if set. */ + reg.pc = reg.spc; + } else { + /* Calculate the PC from the size of the instruction + that the delay slot we're in belongs to. */ + reg.pc += insn_size(reg.erp & ~1) - 1 ; + } + } + + if ((reg.exs & 0x3) == 0x0) { + /* Bits 1 - 0 indicate the type of memory operation performed + by the interrupted instruction. 0 means no memory operation, + and EDA is undefined in that case. We zero it to avoid confusion. */ + reg.eda = 0; + } + + if (sigval == SIGTRAP) { + /* Break 8, single step or hardware breakpoint exception. */ + + /* Check IDX field of EXS. */ + if (((reg.exs & 0xff00) >> 8) == 0x18) { + + /* Break 8. */ + + /* Static (compiled) breakpoints must return to the next instruction + in order to avoid infinite loops (default value of ERP). Dynamic + (gdb-invoked) must subtract the size of the break instruction from + the ERP so that the instruction that was originally in the break + instruction's place will be run when we return from the exception. */ + if (!dynamic_bp) { + /* Assuming that all breakpoints are dynamic from now on. */ + dynamic_bp = 1; + } else { + + /* Only if not in a delay slot. */ + if (!(reg.erp & 0x1)) { + reg.erp -= 2; + reg.pc -= 2; + } + } + + } else if (((reg.exs & 0xff00) >> 8) == 0x3) { + /* Single step. */ + /* Don't fiddle with S1. */ + + } else if (((reg.exs & 0xff00) >> 8) == 0xc) { + + /* Hardware watchpoint exception. */ + + /* SPC has been updated so that we will get a single step exception + when we return, but we don't want that. */ + reg.spc = 0; + + /* Don't fiddle with S1. */ + } + + } else if (sigval == SIGINT) { + /* Nothing special. */ + } +} + +static void insert_watchpoint(char type, int addr, int len) +{ + /* Breakpoint/watchpoint types (GDB terminology): + 0 = memory breakpoint for instructions + (not supported; done via memory write instead) + 1 = hardware breakpoint for instructions (supported) + 2 = write watchpoint (supported) + 3 = read watchpoint (supported) + 4 = access watchpoint (supported) */ + + if (type < '1' || type > '4') { + output_buffer[0] = 0; + return; + } + + /* Read watchpoints are set as access watchpoints, because of GDB's + inability to deal with pure read watchpoints. */ + if (type == '3') + type = '4'; + + if (type == '1') { + /* Hardware (instruction) breakpoint. */ + /* Bit 0 in BP_CTRL holds the configuration for I0. */ + if (sreg.s0_3 & 0x1) { + /* Already in use. */ + gdb_cris_strcpy(output_buffer, error_message[E04]); + return; + } + /* Configure. */ + sreg.s1_3 = addr; + sreg.s2_3 = (addr + len - 1); + sreg.s0_3 |= 1; + } else { + int bp; + unsigned int *bp_d_regs = &sreg.s3_3; + + /* The watchpoint allocation scheme is the simplest possible. + For example, if a region is watched for read and + a write watch is requested, a new watchpoint will + be used. Also, if a watch for a region that is already + covered by one or more existing watchpoints, a new + watchpoint will be used. */ + + /* First, find a free data watchpoint. */ + for (bp = 0; bp < 6; bp++) { + /* Each data watchpoint's control registers occupy 2 bits + (hence the 3), starting at bit 2 for D0 (hence the 2) + with 4 bits between for each watchpoint (yes, the 4). */ + if (!(sreg.s0_3 & (0x3 << (2 + (bp * 4))))) { + break; + } + } + + if (bp > 5) { + /* We're out of watchpoints. */ + gdb_cris_strcpy(output_buffer, error_message[E04]); + return; + } + + /* Configure the control register first. */ + if (type == '3' || type == '4') { + /* Trigger on read. */ + sreg.s0_3 |= (1 << (2 + bp * 4)); + } + if (type == '2' || type == '4') { + /* Trigger on write. */ + sreg.s0_3 |= (2 << (2 + bp * 4)); + } + + /* Ugly pointer arithmetics to configure the watched range. */ + bp_d_regs[bp * 2] = addr; + bp_d_regs[bp * 2 + 1] = (addr + len - 1); + } + + /* Set the S1 flag to enable watchpoints. */ + reg.ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT)); + gdb_cris_strcpy(output_buffer, "OK"); +} + +static void remove_watchpoint(char type, int addr, int len) +{ + /* Breakpoint/watchpoint types: + 0 = memory breakpoint for instructions + (not supported; done via memory write instead) + 1 = hardware breakpoint for instructions (supported) + 2 = write watchpoint (supported) + 3 = read watchpoint (supported) + 4 = access watchpoint (supported) */ + if (type < '1' || type > '4') { + output_buffer[0] = 0; + return; + } + + /* Read watchpoints are set as access watchpoints, because of GDB's + inability to deal with pure read watchpoints. */ + if (type == '3') + type = '4'; + + if (type == '1') { + /* Hardware breakpoint. */ + /* Bit 0 in BP_CTRL holds the configuration for I0. */ + if (!(sreg.s0_3 & 0x1)) { + /* Not in use. */ + gdb_cris_strcpy(output_buffer, error_message[E04]); + return; + } + /* Deconfigure. */ + sreg.s1_3 = 0; + sreg.s2_3 = 0; + sreg.s0_3 &= ~1; + } else { + int bp; + unsigned int *bp_d_regs = &sreg.s3_3; + /* Try to find a watchpoint that is configured for the + specified range, then check that read/write also matches. */ + + /* Ugly pointer arithmetic, since I cannot rely on a + single switch (addr) as there may be several watchpoints with + the same start address for example. */ + + for (bp = 0; bp < 6; bp++) { + if (bp_d_regs[bp * 2] == addr && + bp_d_regs[bp * 2 + 1] == (addr + len - 1)) { + /* Matching range. */ + int bitpos = 2 + bp * 4; + int rw_bits; + + /* Read/write bits for this BP. */ + rw_bits = (sreg.s0_3 & (0x3 << bitpos)) >> bitpos; + + if ((type == '3' && rw_bits == 0x1) || + (type == '2' && rw_bits == 0x2) || + (type == '4' && rw_bits == 0x3)) { + /* Read/write matched. */ + break; + } + } + } + + if (bp > 5) { + /* No watchpoint matched. */ + gdb_cris_strcpy(output_buffer, error_message[E04]); + return; + } + + /* Found a matching watchpoint. Now, deconfigure it by + both disabling read/write in bp_ctrl and zeroing its + start/end addresses. */ + sreg.s0_3 &= ~(3 << (2 + (bp * 4))); + bp_d_regs[bp * 2] = 0; + bp_d_regs[bp * 2 + 1] = 0; + } + + /* Note that we don't clear the S1 flag here. It's done when continuing. */ + gdb_cris_strcpy(output_buffer, "OK"); +} + + + +/* All expected commands are sent from remote.c. Send a response according + to the description in remote.c. */ +void +handle_exception(int sigval) +{ + /* Avoid warning of not used. */ + + USEDFUN(handle_exception); + USEDVAR(internal_stack[0]); + + register_fixup(sigval); + + /* Send response. */ + stub_is_stopped(sigval); + + for (;;) { + output_buffer[0] = '\0'; + getpacket(input_buffer); + switch (input_buffer[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. */ + { + char *buf; + /* General and special registers. */ + buf = mem2hex(output_buffer, (char *)®, sizeof(registers)); + /* Support registers. */ + /* -1 because of the null termination that mem2hex adds. */ + mem2hex(buf, + (char *)&sreg + (reg.srs * 16 * sizeof(unsigned int)), + 16 * sizeof(unsigned int)); + break; + } + case 'G': + /* Write registers. GXX..XX + Each byte of register data is described by two hex digits. + Success: OK + Failure: void. */ + /* General and special registers. */ + hex2mem((char *)®, &input_buffer[1], sizeof(registers)); + /* Support registers. */ + hex2mem((char *)&sreg + (reg.srs * 16 * sizeof(unsigned int)), + &input_buffer[1] + sizeof(registers), + 16 * sizeof(unsigned int)); + gdb_cris_strcpy(output_buffer, "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(&input_buffer[1], &suffix, 16); + int status; + + status = write_register(regno, suffix+1); + + switch (status) { + case E02: + /* Do not support read-only registers. */ + gdb_cris_strcpy(output_buffer, error_message[E02]); + break; + case E05: + /* Do not support non-existing registers. */ + gdb_cris_strcpy(output_buffer, error_message[E05]); + break; + default: + /* Valid register number. */ + gdb_cris_strcpy(output_buffer, "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(&input_buffer[1], + &suffix, 16); + int len = gdb_cris_strtol(suffix+1, 0, 16); + + /* Bogus read (i.e. outside the kernel's + segment)? . */ + if (!((unsigned int)addr >= 0xc0000000 && + (unsigned int)addr < 0xd0000000)) + addr = NULL; + + mem2hex(output_buffer, addr, len); + } + 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(&input_buffer[1], + &lenptr, 16); + int len = gdb_cris_strtol(lenptr+1, &dataptr, 16); + if (*lenptr == ',' && *dataptr == ':') { + if (input_buffer[0] == 'M') { + hex2mem(addr, dataptr + 1, len); + } else /* X */ { + bin2mem(addr, dataptr + 1, len); + } + gdb_cris_strcpy(output_buffer, "OK"); + } + else { + gdb_cris_strcpy(output_buffer, 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 (input_buffer[1] != '\0') { + /* FIXME: Doesn't handle address argument. */ + gdb_cris_strcpy(output_buffer, error_message[E04]); + break; + } + + /* Before continuing, make sure everything is set up correctly. */ + + /* Set the SPC to some unlikely value. */ + reg.spc = 0; + /* Set the S1 flag to 0 unless some watchpoint is enabled (since setting + S1 to 0 would also disable watchpoints). (Note that bits 26-31 in BP_CTRL + are reserved, so don't check against those). */ + if ((sreg.s0_3 & 0x3fff) == 0) { + reg.ccs &= ~(1 << (S_CCS_BITNR + CCS_SHIFT)); + } + + 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. */ + + if (input_buffer[1] != '\0') { + /* FIXME: Doesn't handle address argument. */ + gdb_cris_strcpy(output_buffer, error_message[E04]); + break; + } + + /* Set the SPC to PC, which is where we'll return + (deduced previously). */ + reg.spc = reg.pc; + + /* Set the S1 (first stacked, not current) flag, which will + kick into action when we rfe. */ + reg.ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT)); + return; + + case 'Z': + + /* Insert breakpoint or watchpoint, Ztype,addr,length. + Remote protocol says: A remote target shall return an empty string + for an unrecognized breakpoint or watchpoint packet type. */ + { + char *lenptr; + char *dataptr; + int addr = gdb_cris_strtol(&input_buffer[3], &lenptr, 16); + int len = gdb_cris_strtol(lenptr + 1, &dataptr, 16); + char type = input_buffer[1]; + + insert_watchpoint(type, addr, len); + break; + } + + case 'z': + /* Remove breakpoint or watchpoint, Ztype,addr,length. + Remote protocol says: A remote target shall return an empty string + for an unrecognized breakpoint or watchpoint packet type. */ + { + char *lenptr; + char *dataptr; + int addr = gdb_cris_strtol(&input_buffer[3], &lenptr, 16); + int len = gdb_cris_strtol(lenptr + 1, &dataptr, 16); + char type = input_buffer[1]; + + remove_watchpoint(type, addr, len); + break; + } + + + case '?': + /* The last signal which caused a stop. ? + Success: SAA, where AA is the signal number. + Failure: void. */ + output_buffer[0] = 'S'; + output_buffer[1] = highhex(sigval); + output_buffer[2] = lowhex(sigval); + output_buffer[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 */ + + /* FIXME: What's the difference between not supported + and ignored (below)? */ + gdb_cris_strcpy(output_buffer, 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. */ + output_buffer[0] = 0; + break; + } + putpacket(output_buffer); + } +} + +void +kgdb_init(void) +{ + reg_intr_vect_rw_mask intr_mask; + reg_ser_rw_intr_mask ser_intr_mask; + + /* Configure the kgdb serial port. */ +#if defined(CONFIG_ETRAX_KGDB_PORT0) + /* Note: no shortcut registered (not handled by multiple_interrupt). + See entry.S. */ + set_exception_vector(SER0_INTR_VECT, kgdb_handle_exception); + /* Enable the ser irq in the global config. */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser0 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + + ser_intr_mask = REG_RD(ser, regi_ser0, rw_intr_mask); + ser_intr_mask.data_avail = regk_ser_yes; + REG_WR(ser, regi_ser0, rw_intr_mask, ser_intr_mask); +#elif defined(CONFIG_ETRAX_KGDB_PORT1) + /* Note: no shortcut registered (not handled by multiple_interrupt). + See entry.S. */ + set_exception_vector(SER1_INTR_VECT, kgdb_handle_exception); + /* Enable the ser irq in the global config. */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser1 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + + ser_intr_mask = REG_RD(ser, regi_ser1, rw_intr_mask); + ser_intr_mask.data_avail = regk_ser_yes; + REG_WR(ser, regi_ser1, rw_intr_mask, ser_intr_mask); +#elif defined(CONFIG_ETRAX_KGDB_PORT2) + /* Note: no shortcut registered (not handled by multiple_interrupt). + See entry.S. */ + set_exception_vector(SER2_INTR_VECT, kgdb_handle_exception); + /* Enable the ser irq in the global config. */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser2 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + + ser_intr_mask = REG_RD(ser, regi_ser2, rw_intr_mask); + ser_intr_mask.data_avail = regk_ser_yes; + REG_WR(ser, regi_ser2, rw_intr_mask, ser_intr_mask); +#elif defined(CONFIG_ETRAX_KGDB_PORT3) + /* Note: no shortcut registered (not handled by multiple_interrupt). + See entry.S. */ + set_exception_vector(SER3_INTR_VECT, kgdb_handle_exception); + /* Enable the ser irq in the global config. */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.ser3 = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + + ser_intr_mask = REG_RD(ser, regi_ser3, rw_intr_mask); + ser_intr_mask.data_avail = regk_ser_yes; + REG_WR(ser, regi_ser3, rw_intr_mask, ser_intr_mask); +#endif + +} +/* Performs a complete re-start from scratch. */ +static void +kill_restart(void) +{ + machine_restart(""); +} + +/* Use this static breakpoint in the start-up only. */ + +void +breakpoint(void) +{ + kgdb_started = 1; + dynamic_bp = 0; /* This is a static, not a dynamic breakpoint. */ + __asm__ volatile ("break 8"); /* Jump to kgdb_handle_breakpoint. */ +} + +/****************************** End of file **********************************/ diff --git a/arch/cris/arch-v32/kernel/kgdb_asm.S b/arch/cris/arch-v32/kernel/kgdb_asm.S new file mode 100644 index 0000000..b350dd2 --- /dev/null +++ b/arch/cris/arch-v32/kernel/kgdb_asm.S @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2004 Axis Communications AB + * + * Code for handling break 8, hardware breakpoint, single step, and serial + * port exceptions for kernel debugging purposes. + */ + +#include <linux/config.h> +#include <asm/arch/hwregs/intr_vect.h> + + ;; Exported functions. + .globl kgdb_handle_exception + +kgdb_handle_exception: + +;; Create a register image of the caller. +;; +;; First of all, save the ACR on the stack since we need it for address calculations. +;; We put it into the register struct later. + + subq 4, $sp + move.d $acr, [$sp] + +;; Now we are free to use ACR all we want. +;; If we were running this handler with interrupts on, we would have to be careful +;; to save and restore CCS manually, but since we aren't we treat it like every other +;; register. + + move.d reg, $acr + move.d $r0, [$acr] ; Save R0 (start of register struct) + addq 4, $acr + move.d $r1, [$acr] ; Save R1 + addq 4, $acr + move.d $r2, [$acr] ; Save R2 + addq 4, $acr + move.d $r3, [$acr] ; Save R3 + addq 4, $acr + move.d $r4, [$acr] ; Save R4 + addq 4, $acr + move.d $r5, [$acr] ; Save R5 + addq 4, $acr + move.d $r6, [$acr] ; Save R6 + addq 4, $acr + move.d $r7, [$acr] ; Save R7 + addq 4, $acr + move.d $r8, [$acr] ; Save R8 + addq 4, $acr + move.d $r9, [$acr] ; Save R9 + addq 4, $acr + move.d $r10, [$acr] ; Save R10 + addq 4, $acr + move.d $r11, [$acr] ; Save R11 + addq 4, $acr + move.d $r12, [$acr] ; Save R12 + addq 4, $acr + move.d $r13, [$acr] ; Save R13 + addq 4, $acr + move.d $sp, [$acr] ; Save SP (R14) + addq 4, $acr + + ;; The ACR register is already saved on the stack, so pop it from there. + move.d [$sp],$r0 + move.d $r0, [$acr] + addq 4, $acr + + move $bz, [$acr] + addq 1, $acr + move $vr, [$acr] + addq 1, $acr + move $pid, [$acr] + addq 4, $acr + move $srs, [$acr] + addq 1, $acr + move $wz, [$acr] + addq 2, $acr + move $exs, [$acr] + addq 4, $acr + move $eda, [$acr] + addq 4, $acr + move $mof, [$acr] + addq 4, $acr + move $dz, [$acr] + addq 4, $acr + move $ebp, [$acr] + addq 4, $acr + move $erp, [$acr] + addq 4, $acr + move $srp, [$acr] + addq 4, $acr + move $nrp, [$acr] + addq 4, $acr + move $ccs, [$acr] + addq 4, $acr + move $usp, [$acr] + addq 4, $acr + move $spc, [$acr] + addq 4, $acr + +;; Skip the pseudo-PC. + addq 4, $acr + +;; Save the support registers in bank 0 - 3. + clear.d $r1 ; Bank counter + move.d sreg, $acr + +;; Bank 0 + move $r1, $srs + nop + nop + nop + move $s0, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s1, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s2, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s3, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s4, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s5, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s6, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s7, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s8, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s9, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s10, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s11, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s12, $r0 + move.d $r0, [$acr] + addq 4, $acr + + ;; Nothing in S13 - S15, bank 0 + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + +;; Bank 1 and bank 2 have the same layout, hence the loop. + addq 1, $r1 +1: + move $r1, $srs + nop + nop + nop + move $s0, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s1, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s2, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s3, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s4, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s5, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s6, $r0 + move.d $r0, [$acr] + addq 4, $acr + + ;; Nothing in S7 - S15, bank 1 and 2 + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + clear.d [$acr] + addq 4, $acr + + addq 1, $r1 + cmpq 3, $r1 + bne 1b + nop + +;; Bank 3 + move $r1, $srs + nop + nop + nop + move $s0, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s1, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s2, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s3, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s4, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s5, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s6, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s7, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s8, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s9, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s10, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s11, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s12, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s13, $r0 + move.d $r0, [$acr] + addq 4, $acr + move $s14, $r0 + move.d $r0, [$acr] + addq 4, $acr +;; Nothing in S15, bank 3 + clear.d [$acr] + addq 4, $acr + +;; Check what got us here: get IDX field of EXS. + move $exs, $r10 + and.d 0xff00, $r10 + lsrq 8, $r10 +#if defined(CONFIG_ETRAX_KGDB_PORT0) + cmp.d SER0_INTR_VECT, $r10 ; IRQ for serial port 0 + beq sigint + nop +#elif defined(CONFIG_ETRAX_KGDB_PORT1) + cmp.d SER1_INTR_VECT, $r10 ; IRQ for serial port 1 + beq sigint + nop +#elif defined(CONFIG_ETRAX_KGDB_PORT2) + cmp.d SER2_INTR_VECT, $r10 ; IRQ for serial port 2 + beq sigint + nop +#elif defined(CONFIG_ETRAX_KGDB_PORT3) + cmp.d SER3_INTR_VECT, $r10 ; IRQ for serial port 3 + beq sigint + nop +#endif +;; Multiple interrupt must be due to serial break. + cmp.d 0x30, $r10 ; Multiple interrupt + beq sigint + nop +;; Neither of those? Then it's a sigtrap. + ba handle_comm + moveq 5, $r10 ; Set SIGTRAP (delay slot) + +sigint: + ;; Serial interrupt; get character + jsr getDebugChar + nop ; Delay slot + cmp.b 3, $r10 ; \003 (Ctrl-C)? + bne return ; No, get out of here + nop + moveq 2, $r10 ; Set SIGINT + +;; +;; Handle the communication +;; +handle_comm: + move.d internal_stack+1020, $sp ; Use the internal stack which grows upwards + jsr handle_exception ; Interactive routine + nop + +;; +;; Return to the caller +;; +return: + +;; First of all, write the support registers. + clear.d $r1 ; Bank counter + move.d sreg, $acr + +;; Bank 0 + move $r1, $srs + nop + nop + nop + move.d [$acr], $r0 + move $r0, $s0 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s1 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s2 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s3 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s4 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s5 + addq 4, $acr + +;; Nothing in S6 - S7, bank 0. + addq 4, $acr + addq 4, $acr + + move.d [$acr], $r0 + move $r0, $s8 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s9 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s10 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s11 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s12 + addq 4, $acr + +;; Nothing in S13 - S15, bank 0 + addq 4, $acr + addq 4, $acr + addq 4, $acr + +;; Bank 1 and bank 2 have the same layout, hence the loop. + addq 1, $r1 +2: + move $r1, $srs + nop + nop + nop + move.d [$acr], $r0 + move $r0, $s0 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s1 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s2 + addq 4, $acr + +;; S3 (MM_CAUSE) is read-only. + addq 4, $acr + + move.d [$acr], $r0 + move $r0, $s4 + addq 4, $acr + +;; FIXME: Actually write S5/S6? (Affects MM_CAUSE.) + addq 4, $acr + addq 4, $acr + +;; Nothing in S7 - S15, bank 1 and 2 + addq 4, $acr + addq 4, $acr + addq 4, $acr + addq 4, $acr + addq 4, $acr + addq 4, $acr + addq 4, $acr + addq 4, $acr + addq 4, $acr + + addq 1, $r1 + cmpq 3, $r1 + bne 2b + nop + +;; Bank 3 + move $r1, $srs + nop + nop + nop + move.d [$acr], $r0 + move $r0, $s0 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s1 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s2 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s3 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s4 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s5 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s6 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s7 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s8 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s9 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s10 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s11 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s12 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s13 + addq 4, $acr + move.d [$acr], $r0 + move $r0, $s14 + addq 4, $acr + +;; Nothing in S15, bank 3 + addq 4, $acr + +;; Now, move on to the regular register restoration process. + + move.d reg, $acr ; Reset ACR to point at the beginning of the register image + move.d [$acr], $r0 ; Restore R0 + addq 4, $acr + move.d [$acr], $r1 ; Restore R1 + addq 4, $acr + move.d [$acr], $r2 ; Restore R2 + addq 4, $acr + move.d [$acr], $r3 ; Restore R3 + addq 4, $acr + move.d [$acr], $r4 ; Restore R4 + addq 4, $acr + move.d [$acr], $r5 ; Restore R5 + addq 4, $acr + move.d [$acr], $r6 ; Restore R6 + addq 4, $acr + move.d [$acr], $r7 ; Restore R7 + addq 4, $acr + move.d [$acr], $r8 ; Restore R8 + addq 4, $acr + move.d [$acr], $r9 ; Restore R9 + addq 4, $acr + move.d [$acr], $r10 ; Restore R10 + addq 4, $acr + move.d [$acr], $r11 ; Restore R11 + addq 4, $acr + move.d [$acr], $r12 ; Restore R12 + addq 4, $acr + move.d [$acr], $r13 ; Restore R13 + +;; +;; We restore all registers, even though some of them probably haven't changed. +;; + + addq 4, $acr + move.d [$acr], $sp ; Restore SP (R14) + + ;; ACR cannot be restored just yet. + addq 8, $acr + + ;; Skip BZ, VR. + addq 2, $acr + + move [$acr], $pid ; Restore PID + addq 4, $acr + move [$acr], $srs ; Restore SRS + nop + nop + nop + addq 1, $acr + + ;; Skip WZ. + addq 2, $acr + + move [$acr], $exs ; Restore EXS. + addq 4, $acr + move [$acr], $eda ; Restore EDA. + addq 4, $acr + move [$acr], $mof ; Restore MOF. + + ;; Skip DZ. + addq 8, $acr + + move [$acr], $ebp ; Restore EBP. + addq 4, $acr + move [$acr], $erp ; Restore ERP. + addq 4, $acr + move [$acr], $srp ; Restore SRP. + addq 4, $acr + move [$acr], $nrp ; Restore NRP. + addq 4, $acr + move [$acr], $ccs ; Restore CCS like an ordinary register. + addq 4, $acr + move [$acr], $usp ; Restore USP + addq 4, $acr + move [$acr], $spc ; Restore SPC + ; No restoration of pseudo-PC of course. + + move.d reg, $acr ; Reset ACR to point at the beginning of the register image + add.d 15*4, $acr + move.d [$acr], $acr ; Finally, restore ACR. + rete ; Same as jump ERP + rfe ; Shifts CCS diff --git a/arch/cris/arch-v32/kernel/pinmux.c b/arch/cris/arch-v32/kernel/pinmux.c new file mode 100644 index 0000000..a2b8aa3 --- /dev/null +++ b/arch/cris/arch-v32/kernel/pinmux.c @@ -0,0 +1,229 @@ +/* + * Allocator for I/O pins. All pins are allocated to GPIO at bootup. + * Unassigned pins and GPIO pins can be allocated to a fixed interface + * or the I/O processor instead. + * + * Copyright (c) 2004 Axis Communications AB. + */ + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/hwregs/pinmux_defs.h> + +#undef DEBUG + +#define PORT_PINS 18 +#define PORTS 4 + +static char pins[PORTS][PORT_PINS]; +static DEFINE_SPINLOCK(pinmux_lock); + +static void crisv32_pinmux_set(int port); + +int +crisv32_pinmux_init(void) +{ + static int initialized = 0; + + if (!initialized) { + reg_pinmux_rw_pa pa = REG_RD(pinmux, regi_pinmux, rw_pa); + initialized = 1; + pa.pa0 = pa.pa1 = pa.pa2 = pa.pa3 = + pa.pa4 = pa.pa5 = pa.pa6 = pa.pa7 = regk_pinmux_yes; + REG_WR(pinmux, regi_pinmux, rw_pa, pa); + crisv32_pinmux_alloc(PORT_B, 0, PORT_PINS - 1, pinmux_gpio); + crisv32_pinmux_alloc(PORT_C, 0, PORT_PINS - 1, pinmux_gpio); + crisv32_pinmux_alloc(PORT_D, 0, PORT_PINS - 1, pinmux_gpio); + crisv32_pinmux_alloc(PORT_E, 0, PORT_PINS - 1, pinmux_gpio); + } + + return 0; +} + +int +crisv32_pinmux_alloc(int port, int first_pin, int last_pin, enum pin_mode mode) +{ + int i; + unsigned long flags; + + crisv32_pinmux_init(); + + if (port > PORTS) + return -EINVAL; + + spin_lock_irqsave(&pinmux_lock, flags); + + for (i = first_pin; i <= last_pin; i++) + { + if ((pins[port][i] != pinmux_none) && (pins[port][i] != pinmux_gpio) && + (pins[port][i] != mode)) + { + spin_unlock_irqrestore(&pinmux_lock, flags); +#ifdef DEBUG + panic("Pinmux alloc failed!\n"); +#endif + return -EPERM; + } + } + + for (i = first_pin; i <= last_pin; i++) + pins[port][i] = mode; + + crisv32_pinmux_set(port); + + spin_unlock_irqrestore(&pinmux_lock, flags); + + return 0; +} + +int +crisv32_pinmux_alloc_fixed(enum fixed_function function) +{ + int ret = -EINVAL; + char saved[sizeof pins]; + unsigned long flags; + + spin_lock_irqsave(&pinmux_lock, flags); + + /* Save internal data for recovery */ + memcpy(saved, pins, sizeof pins); + + reg_pinmux_rw_hwprot hwprot = REG_RD(pinmux, regi_pinmux, rw_hwprot); + + switch(function) + { + case pinmux_ser1: + ret = crisv32_pinmux_alloc(PORT_C, 4, 7, pinmux_fixed); + hwprot.ser1 = regk_pinmux_yes; + break; + case pinmux_ser2: + ret = crisv32_pinmux_alloc(PORT_C, 8, 11, pinmux_fixed); + hwprot.ser2 = regk_pinmux_yes; + break; + case pinmux_ser3: + ret = crisv32_pinmux_alloc(PORT_C, 12, 15, pinmux_fixed); + hwprot.ser3 = regk_pinmux_yes; + break; + case pinmux_sser0: + ret = crisv32_pinmux_alloc(PORT_C, 0, 3, pinmux_fixed); + ret |= crisv32_pinmux_alloc(PORT_C, 16, 16, pinmux_fixed); + hwprot.sser0 = regk_pinmux_yes; + break; + case pinmux_sser1: + ret = crisv32_pinmux_alloc(PORT_D, 0, 4, pinmux_fixed); + hwprot.sser1 = regk_pinmux_yes; + break; + case pinmux_ata0: + ret = crisv32_pinmux_alloc(PORT_D, 5, 7, pinmux_fixed); + ret |= crisv32_pinmux_alloc(PORT_D, 15, 17, pinmux_fixed); + hwprot.ata0 = regk_pinmux_yes; + break; + case pinmux_ata1: + ret = crisv32_pinmux_alloc(PORT_D, 0, 4, pinmux_fixed); + ret |= crisv32_pinmux_alloc(PORT_E, 17, 17, pinmux_fixed); + hwprot.ata1 = regk_pinmux_yes; + break; + case pinmux_ata2: + ret = crisv32_pinmux_alloc(PORT_C, 11, 15, pinmux_fixed); + ret |= crisv32_pinmux_alloc(PORT_E, 3, 3, pinmux_fixed); + hwprot.ata2 = regk_pinmux_yes; + break; + case pinmux_ata3: + ret = crisv32_pinmux_alloc(PORT_C, 8, 10, pinmux_fixed); + ret |= crisv32_pinmux_alloc(PORT_C, 0, 2, pinmux_fixed); + hwprot.ata2 = regk_pinmux_yes; + break; + case pinmux_ata: + ret = crisv32_pinmux_alloc(PORT_B, 0, 15, pinmux_fixed); + ret |= crisv32_pinmux_alloc(PORT_D, 8, 15, pinmux_fixed); + hwprot.ata = regk_pinmux_yes; + break; + case pinmux_eth1: + ret = crisv32_pinmux_alloc(PORT_E, 0, 17, pinmux_fixed); + hwprot.eth1 = regk_pinmux_yes; + hwprot.eth1_mgm = regk_pinmux_yes; + break; + case pinmux_timer: + ret = crisv32_pinmux_alloc(PORT_C, 16, 16, pinmux_fixed); + hwprot.timer = regk_pinmux_yes; + spin_unlock_irqrestore(&pinmux_lock, flags); + return ret; + } + + if (!ret) + REG_WR(pinmux, regi_pinmux, rw_hwprot, hwprot); + else + memcpy(pins, saved, sizeof pins); + + spin_unlock_irqrestore(&pinmux_lock, flags); + + return ret; +} + +void +crisv32_pinmux_set(int port) +{ + int i; + int gpio_val = 0; + int iop_val = 0; + + for (i = 0; i < PORT_PINS; i++) + { + if (pins[port][i] == pinmux_gpio) + gpio_val |= (1 << i); + else if (pins[port][i] == pinmux_iop) + iop_val |= (1 << i); + } + + REG_WRITE(int, regi_pinmux + REG_RD_ADDR_pinmux_rw_pb_gio + 8*port, gpio_val); + REG_WRITE(int, regi_pinmux + REG_RD_ADDR_pinmux_rw_pb_iop + 8*port, iop_val); + +#ifdef DEBUG + crisv32_pinmux_dump(); +#endif +} + +int +crisv32_pinmux_dealloc(int port, int first_pin, int last_pin) +{ + int i; + unsigned long flags; + + crisv32_pinmux_init(); + + if (port > PORTS) + return -EINVAL; + + spin_lock_irqsave(&pinmux_lock, flags); + + for (i = first_pin; i <= last_pin; i++) + pins[port][i] = pinmux_none; + + crisv32_pinmux_set(port); + spin_unlock_irqrestore(&pinmux_lock, flags); + + return 0; +} + +void +crisv32_pinmux_dump(void) +{ + int i, j; + + crisv32_pinmux_init(); + + for (i = 0; i < PORTS; i++) + { + printk("Port %c\n", 'B'+i); + for (j = 0; j < PORT_PINS; j++) + printk(" Pin %d = %d\n", j, pins[i][j]); + } +} + +__initcall(crisv32_pinmux_init); diff --git a/arch/cris/arch-v32/kernel/process.c b/arch/cris/arch-v32/kernel/process.c new file mode 100644 index 0000000..882be42 --- /dev/null +++ b/arch/cris/arch-v32/kernel/process.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2000-2003 Axis Communications AB + * + * Authors: Bjorn Wesen (bjornw@axis.com) + * Mikael Starvik (starvik@axis.com) + * Tobias Anderberg (tobiasa@axis.com), CRISv32 port. + * + * This file handles the architecture-dependent parts of process handling.. + */ + +#include <linux/config.h> +#include <linux/sched.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/timer_defs.h> +#include <asm/arch/hwregs/intr_vect_defs.h> + +extern void stop_watchdog(void); + +#ifdef CONFIG_ETRAX_GPIO +extern void etrax_gpio_wake_up_check(void); /* Defined in drivers/gpio.c. */ +#endif + +extern int cris_hlt_counter; + +/* We use this if we don't have any better idle routine. */ +void default_idle(void) +{ + local_irq_disable(); + if (!need_resched() && !cris_hlt_counter) { + /* Halt until exception. */ + __asm__ volatile("ei \n\t" + "halt "); + } + local_irq_enable(); +} + +/* + * Free current thread data structures etc.. + */ + +extern void deconfigure_bp(long pid); +void exit_thread(void) +{ + deconfigure_bp(current->pid); +} + +/* + * If the watchdog is enabled, disable interrupts and enter an infinite loop. + * The watchdog will reset the CPU after 0.1s. If the watchdog isn't enabled + * then enable it and wait. + */ +extern void arch_enable_nmi(void); + +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 +{ + reg_timer_rw_wd_ctrl wd_ctrl = {0}; + + stop_watchdog(); + + wd_ctrl.key = 16; /* Arbitrary key. */ + wd_ctrl.cnt = 1; /* Minimum time. */ + wd_ctrl.cmd = regk_timer_start; + + arch_enable_nmi(); + REG_WR(timer, regi_timer, rw_wd_ctrl, wd_ctrl); +} +#endif + + while (1) + ; /* Wait for reset. */ +} + +/* + * Return saved PC of a blocked thread. + */ +unsigned long thread_saved_pc(struct task_struct *t) +{ + return (unsigned long)user_regs(t->thread_info)->erp; +} + +static void +kernel_thread_helper(void* dummy, int (*fn)(void *), void * arg) +{ + fn(arg); + do_exit(-1); /* Should never be called, return bad exit value. */ +} + +/* Create a kernel thread. */ +int +kernel_thread(int (*fn)(void *), void * arg, unsigned long flags) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + + /* Don't use r10 since that is set to 0 in copy_thread. */ + regs.r11 = (unsigned long) fn; + regs.r12 = (unsigned long) arg; + regs.erp = (unsigned long) kernel_thread_helper; + regs.ccs = 1 << (I_CCS_BITNR + CCS_SHIFT); + + /* Create the new process. */ + return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); +} + +/* + * Setup the child's kernel stack with a pt_regs and call switch_stack() on it. + * It will be unnested 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. + */ + +extern asmlinkage void ret_from_fork(void); + +int +copy_thread(int nr, unsigned long clone_flags, unsigned long usp, + unsigned long unused, + struct task_struct *p, struct pt_regs *regs) +{ + struct pt_regs *childregs; + struct switch_stack *swstack; + + /* + * Put the pt_regs structure at the end of the new kernel stack page and + * fix it up. Note: the task_struct doubles as the kernel stack for the + * task. + */ + childregs = user_regs(p->thread_info); + *childregs = *regs; /* Struct copy of pt_regs. */ + p->set_child_tid = p->clear_child_tid = NULL; + childregs->r10 = 0; /* Child returns 0 after a fork/clone. */ + + /* Set a new TLS ? + * The TLS is in $mof beacuse it is the 5th argument to sys_clone. + */ + if (p->mm && (clone_flags & CLONE_SETTLS)) { + p->thread_info->tls = regs->mof; + } + + /* Put the switch stack right below the pt_regs. */ + swstack = ((struct switch_stack *) childregs) - 1; + + /* Paramater to ret_from_sys_call. 0 is don't restart the syscall. */ + swstack->r9 = 0; + + /* + * We want to return into ret_from_sys_call after the _resume. + * ret_from_fork will call ret_from_sys_call. + */ + swstack->return_ip = (unsigned long) ret_from_fork; + + /* Fix the user-mode and kernel-mode stackpointer. */ + p->thread.usp = usp; + p->thread.ksp = (unsigned long) swstack; + + return 0; +} + +/* + * Be aware of the "magic" 7th argument in the four system-calls below. + * They need the latest stackframe, which is put as the 7th argument by + * entry.S. The previous arguments are dummies or actually used, but need + * to be defined to reach the 7th argument. + * + * N.B.: Another method to get the stackframe is to use current_regs(). But + * it returns the latest stack-frame stacked when going from _user mode_ and + * some of these (at least sys_clone) are called from kernel-mode sometimes + * (for example during kernel_thread, above) and thus cannot use it. Thus, + * to be sure not to get any surprises, we use the method for the other calls + * as well. + */ +asmlinkage int +sys_fork(long r10, long r11, long r12, long r13, long mof, long srp, + struct pt_regs *regs) +{ + return do_fork(SIGCHLD, rdusp(), regs, 0, NULL, NULL); +} + +/* FIXME: Is parent_tid/child_tid really third/fourth argument? Update lib? */ +asmlinkage int +sys_clone(unsigned long newusp, unsigned long flags, int *parent_tid, int *child_tid, + unsigned long tls, long srp, struct pt_regs *regs) +{ + if (!newusp) + newusp = rdusp(); + + return do_fork(flags, newusp, regs, 0, parent_tid, child_tid); +} + +/* + * vfork is a system call in i386 because of register-pressure - maybe + * we can remove it and handle it in libc but we put it here until then. + */ +asmlinkage int +sys_vfork(long r10, long r11, long r12, long r13, long mof, long srp, + struct pt_regs *regs) +{ + return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, rdusp(), regs, 0, NULL, NULL); +} + +/* sys_execve() executes a new program. */ +asmlinkage int +sys_execve(const char *fname, char **argv, char **envp, long r13, long mof, long srp, + struct pt_regs *regs) +{ + int error; + char *filename; + + filename = getname(fname); + error = PTR_ERR(filename); + + if (IS_ERR(filename)) + goto out; + + error = do_execve(filename, argv, envp, regs); + putname(filename); + out: + return error; +} + +unsigned long +get_wchan(struct task_struct *p) +{ + /* TODO */ + return 0; +} +#undef last_sched +#undef first_sched + +void show_regs(struct pt_regs * regs) +{ + unsigned long usp = rdusp(); + printk("ERP: %08lx SRP: %08lx CCS: %08lx USP: %08lx MOF: %08lx\n", + regs->erp, regs->srp, regs->ccs, 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-v32/kernel/ptrace.c b/arch/cris/arch-v32/kernel/ptrace.c new file mode 100644 index 0000000..208489d --- /dev/null +++ b/arch/cris/arch-v32/kernel/ptrace.c @@ -0,0 +1,597 @@ +/* + * 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/smp_lock.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/system.h> +#include <asm/processor.h> +#include <asm/arch/hwregs/supp_reg.h> + +/* + * Determines which bits in CCS the user has access to. + * 1 = access, 0 = no access. + */ +#define CCS_MASK 0x00087c00 /* SXNZVC */ + +#define SBIT_USER (1 << (S_CCS_BITNR + CCS_SHIFT)) + +static int put_debugreg(long pid, unsigned int regno, long data); +static long get_debugreg(long pid, unsigned int regno); +static unsigned long get_pseudo_pc(struct task_struct *child); +void deconfigure_bp(long pid); + +extern unsigned long cris_signal_return_page; + +/* + * Get contents of register REGNO in task TASK. + */ +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 + */ + unsigned long ret; + + if (regno <= PT_EDA) + ret = ((unsigned long *)user_regs(task->thread_info))[regno]; + else if (regno == PT_USP) + ret = task->thread.usp; + else if (regno == PT_PPC) + ret = get_pseudo_pc(task); + else if (regno <= PT_MAX) + ret = get_debugreg(task->pid, regno); + else + ret = 0; + + return ret; +} + +/* + * Write contents of register REGNO in task TASK. + */ +int put_reg(struct task_struct *task, unsigned int regno, unsigned long data) +{ + if (regno <= PT_EDA) + ((unsigned long *)user_regs(task->thread_info))[regno] = data; + else if (regno == PT_USP) + task->thread.usp = data; + else if (regno == PT_PPC) { + /* Write pseudo-PC to ERP only if changed. */ + if (data != get_pseudo_pc(task)) + ((unsigned long *)user_regs(task->thread_info))[PT_ERP] = data; + } else if (regno <= PT_MAX) + return put_debugreg(task->pid, 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) +{ + unsigned long tmp; + + /* Deconfigure SPC and S-bit. */ + tmp = get_reg(child, PT_CCS) & ~SBIT_USER; + put_reg(child, PT_CCS, tmp); + put_reg(child, PT_SPC, 0); + + /* Deconfigure any watchpoints associated with the child. */ + deconfigure_bp(child->pid); +} + + +asmlinkage int +sys_ptrace(long request, long pid, long addr, long data) +{ + struct task_struct *child; + int ret; + unsigned long __user *datap = (unsigned long __user *)data; + + lock_kernel(); + ret = -EPERM; + + if (request == PTRACE_TRACEME) { + /* are we already being traced? */ + if (current->ptrace & PT_PTRACED) + goto out; + ret = security_ptrace(current->parent, current); + if (ret) + goto out; + /* set the ptrace bit in the process flags. */ + current->ptrace |= PT_PTRACED; + ret = 0; + goto out; + } + + ret = -ESRCH; + read_lock(&tasklist_lock); + child = find_task_by_pid(pid); + + if (child) + get_task_struct(child); + + read_unlock(&tasklist_lock); + + if (!child) + goto out; + + ret = -EPERM; + + if (pid == 1) /* Leave the init process alone! */ + goto out_tsk; + + if (request == PTRACE_ATTACH) { + ret = ptrace_attach(child); + goto out_tsk; + } + + ret = ptrace_check_attach(child, request == PTRACE_KILL); + if (ret < 0) + goto out_tsk; + + switch (request) { + /* Read word at location address. */ + case PTRACE_PEEKTEXT: + case PTRACE_PEEKDATA: { + unsigned long tmp; + int copied; + + ret = -EIO; + + /* The signal trampoline page is outside the normal user-addressable + * space but still accessible. This is hack to make it possible to + * access the signal handler code in GDB. + */ + if ((addr & PAGE_MASK) == cris_signal_return_page) { + /* The trampoline page is globally mapped, no page table to traverse.*/ + tmp = *(unsigned long*)addr; + } else { + copied = access_process_vm(child, addr, &tmp, sizeof(tmp), 0); + + if (copied != sizeof(tmp)) + break; + } + + ret = put_user(tmp,datap); + break; + } + + /* Read the word at location address in the USER area. */ + case PTRACE_PEEKUSR: { + unsigned long tmp; + + ret = -EIO; + if ((addr & 3) || addr < 0 || addr > PT_MAX << 2) + break; + + tmp = get_reg(child, addr >> 2); + ret = put_user(tmp, datap); + break; + } + + /* Write the word at location address. */ + case PTRACE_POKETEXT: + case PTRACE_POKEDATA: + ret = 0; + + if (access_process_vm(child, addr, &data, sizeof(data), 1) == sizeof(data)) + break; + + ret = -EIO; + break; + + /* Write the word at location address in the USER area. */ + case PTRACE_POKEUSR: + ret = -EIO; + if ((addr & 3) || addr < 0 || addr > PT_MAX << 2) + break; + + addr >>= 2; + + if (addr == PT_CCS) { + /* don't allow the tracing process to change stuff like + * interrupt enable, kernel/user bit, dma enables etc. + */ + data &= CCS_MASK; + data |= get_reg(child, PT_CCS) & ~CCS_MASK; + } + if (put_reg(child, addr, data)) + break; + ret = 0; + break; + + case PTRACE_SYSCALL: + case PTRACE_CONT: + ret = -EIO; + + if (!valid_signal(data)) + break; + + /* Continue means no single-step. */ + put_reg(child, PT_SPC, 0); + + if (!get_debugreg(child->pid, PT_BP_CTRL)) { + unsigned long tmp; + /* If no h/w bp configured, disable S bit. */ + tmp = get_reg(child, PT_CCS) & ~SBIT_USER; + put_reg(child, PT_CCS, tmp); + } + + if (request == PTRACE_SYSCALL) { + set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + } + else { + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + } + + child->exit_code = data; + + /* TODO: make sure any pending breakpoint is killed */ + wake_up_process(child); + ret = 0; + + break; + + /* Make the child exit by sending it a sigkill. */ + case PTRACE_KILL: + ret = 0; + + if (child->exit_state == EXIT_ZOMBIE) + break; + + child->exit_code = SIGKILL; + + /* Deconfigure single-step and h/w bp. */ + ptrace_disable(child); + + /* TODO: make sure any pending breakpoint is killed */ + wake_up_process(child); + break; + + /* Set the trap flag. */ + case PTRACE_SINGLESTEP: { + unsigned long tmp; + ret = -EIO; + + /* Set up SPC if not set already (in which case we have + no other choice but to trust it). */ + if (!get_reg(child, PT_SPC)) { + /* In case we're stopped in a delay slot. */ + tmp = get_reg(child, PT_ERP) & ~1; + put_reg(child, PT_SPC, tmp); + } + tmp = get_reg(child, PT_CCS) | SBIT_USER; + put_reg(child, PT_CCS, tmp); + + if (!valid_signal(data)) + break; + + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); + + /* TODO: set some clever breakpoint mechanism... */ + + child->exit_code = data; + wake_up_process(child); + ret = 0; + break; + + } + case PTRACE_DETACH: + ret = ptrace_detach(child, data); + break; + + /* Get all GP registers from the child. */ + case PTRACE_GETREGS: { + int i; + unsigned long tmp; + + for (i = 0; i <= PT_MAX; i++) { + tmp = get_reg(child, i); + + if (put_user(tmp, datap)) { + ret = -EFAULT; + goto out_tsk; + } + + datap++; + } + + ret = 0; + break; + } + + /* Set all GP registers in the child. */ + case PTRACE_SETREGS: { + int i; + unsigned long tmp; + + for (i = 0; i <= PT_MAX; i++) { + if (get_user(tmp, datap)) { + ret = -EFAULT; + goto out_tsk; + } + + if (i == PT_CCS) { + tmp &= CCS_MASK; + tmp |= get_reg(child, PT_CCS) & ~CCS_MASK; + } + + put_reg(child, i, tmp); + datap++; + } + + ret = 0; + break; + } + + default: + ret = ptrace_request(child, request, addr, data); + break; + } +out_tsk: + put_task_struct(child); +out: + unlock_kernel(); + 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; + } +} + +/* Returns the size of an instruction that has a delay slot. */ + +static int insn_size(struct task_struct *child, unsigned long pc) +{ + unsigned long opcode; + int copied; + int opsize = 0; + + /* Read the opcode at pc (do what PTRACE_PEEKTEXT would do). */ + copied = access_process_vm(child, pc, &opcode, sizeof(opcode), 0); + if (copied != sizeof(opcode)) + return 0; + + switch ((opcode & 0x0f00) >> 8) { + case 0x0: + case 0x9: + case 0xb: + opsize = 2; + break; + case 0xe: + case 0xf: + opsize = 6; + break; + case 0xd: + /* Could be 4 or 6; check more bits. */ + if ((opcode & 0xff) == 0xff) + opsize = 4; + else + opsize = 6; + break; + default: + panic("ERROR: Couldn't find size of opcode 0x%lx at 0x%lx\n", + opcode, pc); + } + + return opsize; +} + +static unsigned long get_pseudo_pc(struct task_struct *child) +{ + /* Default value for PC is ERP. */ + unsigned long pc = get_reg(child, PT_ERP); + + if (pc & 0x1) { + unsigned long spc = get_reg(child, PT_SPC); + /* Delay slot bit set. Report as stopped on proper + instruction. */ + if (spc) { + /* Rely on SPC if set. FIXME: We might want to check + that EXS indicates we stopped due to a single-step + exception. */ + pc = spc; + } else { + /* Calculate the PC from the size of the instruction + that the delay slot we're in belongs to. */ + pc += insn_size(child, pc & ~1) - 1; + } + } + return pc; +} + +static long bp_owner = 0; + +/* Reachable from exit_thread in signal.c, so not static. */ +void deconfigure_bp(long pid) +{ + int bp; + + /* Only deconfigure if the pid is the owner. */ + if (bp_owner != pid) + return; + + for (bp = 0; bp < 6; bp++) { + unsigned long tmp; + /* Deconfigure start and end address (also gets rid of ownership). */ + put_debugreg(pid, PT_BP + 3 + (bp * 2), 0); + put_debugreg(pid, PT_BP + 4 + (bp * 2), 0); + + /* Deconfigure relevant bits in control register. */ + tmp = get_debugreg(pid, PT_BP_CTRL) & ~(3 << (2 + (bp * 4))); + put_debugreg(pid, PT_BP_CTRL, tmp); + } + /* No owner now. */ + bp_owner = 0; +} + +static int put_debugreg(long pid, unsigned int regno, long data) +{ + int ret = 0; + register int old_srs; + +#ifdef CONFIG_ETRAX_KGDB + /* Ignore write, but pretend it was ok if value is 0 + (we don't want POKEUSR/SETREGS failing unnessecarily). */ + return (data == 0) ? ret : -1; +#endif + + /* Simple owner management. */ + if (!bp_owner) + bp_owner = pid; + else if (bp_owner != pid) { + /* Ignore write, but pretend it was ok if value is 0 + (we don't want POKEUSR/SETREGS failing unnessecarily). */ + return (data == 0) ? ret : -1; + } + + /* Remember old SRS. */ + SPEC_REG_RD(SPEC_REG_SRS, old_srs); + /* Switch to BP bank. */ + SUPP_BANK_SEL(BANK_BP); + + switch (regno - PT_BP) { + case 0: + SUPP_REG_WR(0, data); break; + case 1: + case 2: + if (data) + ret = -1; + break; + case 3: + SUPP_REG_WR(3, data); break; + case 4: + SUPP_REG_WR(4, data); break; + case 5: + SUPP_REG_WR(5, data); break; + case 6: + SUPP_REG_WR(6, data); break; + case 7: + SUPP_REG_WR(7, data); break; + case 8: + SUPP_REG_WR(8, data); break; + case 9: + SUPP_REG_WR(9, data); break; + case 10: + SUPP_REG_WR(10, data); break; + case 11: + SUPP_REG_WR(11, data); break; + case 12: + SUPP_REG_WR(12, data); break; + case 13: + SUPP_REG_WR(13, data); break; + case 14: + SUPP_REG_WR(14, data); break; + default: + ret = -1; + break; + } + + /* Restore SRS. */ + SPEC_REG_WR(SPEC_REG_SRS, old_srs); + /* Just for show. */ + NOP(); + NOP(); + NOP(); + + return ret; +} + +static long get_debugreg(long pid, unsigned int regno) +{ + register int old_srs; + register long data; + + if (pid != bp_owner) { + return 0; + } + + /* Remember old SRS. */ + SPEC_REG_RD(SPEC_REG_SRS, old_srs); + /* Switch to BP bank. */ + SUPP_BANK_SEL(BANK_BP); + + switch (regno - PT_BP) { + case 0: + SUPP_REG_RD(0, data); break; + case 1: + case 2: + /* error return value? */ + data = 0; + break; + case 3: + SUPP_REG_RD(3, data); break; + case 4: + SUPP_REG_RD(4, data); break; + case 5: + SUPP_REG_RD(5, data); break; + case 6: + SUPP_REG_RD(6, data); break; + case 7: + SUPP_REG_RD(7, data); break; + case 8: + SUPP_REG_RD(8, data); break; + case 9: + SUPP_REG_RD(9, data); break; + case 10: + SUPP_REG_RD(10, data); break; + case 11: + SUPP_REG_RD(11, data); break; + case 12: + SUPP_REG_RD(12, data); break; + case 13: + SUPP_REG_RD(13, data); break; + case 14: + SUPP_REG_RD(14, data); break; + default: + /* error return value? */ + data = 0; + } + + /* Restore SRS. */ + SPEC_REG_WR(SPEC_REG_SRS, old_srs); + /* Just for show. */ + NOP(); + NOP(); + NOP(); + + return data; +} diff --git a/arch/cris/arch-v32/kernel/setup.c b/arch/cris/arch-v32/kernel/setup.c new file mode 100644 index 0000000..b17a39a --- /dev/null +++ b/arch/cris/arch-v32/kernel/setup.c @@ -0,0 +1,118 @@ +/* + * Display CPU info in /proc/cpuinfo. + * + * Copyright (C) 2003, Axis Communications AB. + */ + +#include <linux/config.h> +#include <linux/seq_file.h> +#include <linux/proc_fs.h> +#include <linux/delay.h> +#include <linux/param.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 + +struct cpu_info { + char *cpu_model; + unsigned short rev; + unsigned short cache_size; + unsigned short flags; +}; + +/* Some of these model are here for historical reasons only. */ +static struct cpu_info cpinfo[] = { + {"ETRAX 1", 0, 0, 0}, + {"ETRAX 2", 1, 0, 0}, + {"ETRAX 3", 2, 0, 0}, + {"ETRAX 4", 3, 0, 0}, + {"Simulator", 7, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA}, + {"ETRAX 100", 8, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_IRQ_BUG}, + {"ETRAX 100", 9, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA}, + + {"ETRAX 100LX", 10, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_USB + | HAS_MMU | HAS_MMU_BUG}, + + {"ETRAX 100LX v2", 11, 8, HAS_ETHERNET100 | HAS_SCSI | HAS_ATA | HAS_USB + | HAS_MMU}, + + {"ETRAX FS", 32, 32, HAS_ETHERNET100 | HAS_ATA | HAS_MMU}, + + {"Unknown", 0, 0, 0} +}; + +int +show_cpuinfo(struct seq_file *m, void *v) +{ + int i; + int cpu = (int)v - 1; + int entries; + unsigned long revision; + struct cpu_info *info; + + entries = sizeof cpinfo / sizeof(struct cpu_info); + info = &cpinfo[entries - 1]; + +#ifdef CONFIG_SMP + if (!cpu_online(cpu)) + return 0; +#endif + + revision = rdvr(); + + for (i = 0; i < entries; i++) { + if (cpinfo[i].rev == revision) { + info = &cpinfo[i]; + break; + } + } + + return seq_printf(m, + "processor\t: %d\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\n", + + cpu, + revision, + info->cpu_model, + info->cache_size, + 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); +} + +#endif /* CONFIG_PROC_FS */ + +void +show_etrax_copyright(void) +{ + printk(KERN_INFO + "Linux/CRISv32 port on ETRAX FS (C) 2003, 2004 Axis Communications AB\n"); +} diff --git a/arch/cris/arch-v32/kernel/signal.c b/arch/cris/arch-v32/kernel/signal.c new file mode 100644 index 0000000..fb4c79d --- /dev/null +++ b/arch/cris/arch-v32/kernel/signal.c @@ -0,0 +1,708 @@ +/* + * Copyright (C) 2003, Axis Communications AB. + */ + +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/ptrace.h> +#include <linux/unistd.h> +#include <linux/stddef.h> +#include <linux/syscalls.h> +#include <linux/vmalloc.h> + +#include <asm/io.h> +#include <asm/processor.h> +#include <asm/ucontext.h> +#include <asm/uaccess.h> +#include <asm/arch/ptrace.h> +#include <asm/arch/hwregs/cpu_vect.h> + +extern unsigned long cris_signal_return_page; + +/* Flag to check if a signal is blockable. */ +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +/* + * A syscall in CRIS is really a "break 13" instruction, which is 2 + * bytes. The registers is manipulated so upon return the instruction + * will be executed again. + * + * This relies on that PC points to the instruction after the break call. + */ +#define RESTART_CRIS_SYS(regs) regs->r10 = regs->orig_r10; regs->erp -= 2; + +/* Signal frames. */ +struct signal_frame { + struct sigcontext sc; + unsigned long extramask[_NSIG_WORDS - 1]; + unsigned char retcode[8]; /* Trampoline code. */ +}; + +struct rt_signal_frame { + struct siginfo *pinfo; + void *puc; + struct siginfo info; + struct ucontext uc; + unsigned char retcode[8]; /* Trampoline code. */ +}; + +int do_signal(int restart, sigset_t *oldset, struct pt_regs *regs); +void keep_debug_flags(unsigned long oldccs, unsigned long oldspc, + struct pt_regs *regs); +/* + * Swap in the new signal mask, and wait for a signal. Define some + * dummy arguments to be able to reach the regs argument. + */ +int +sys_sigsuspend(old_sigset_t mask, long r11, long r12, long r13, long mof, + long srp, struct pt_regs *regs) +{ + sigset_t saveset; + + mask &= _BLOCKABLE; + + spin_lock_irq(¤t->sighand->siglock); + + saveset = current->blocked; + + siginitset(¤t->blocked, mask); + + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + regs->r10 = -EINTR; + + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + + if (do_signal(0, &saveset, regs)) { + /* + * This point is reached twice: once to call + * the signal handler, then again to return + * from the sigsuspend system call. When + * calling the signal handler, R10 hold the + * signal number as set by do_signal(). The + * sigsuspend call will always return with + * the restored value above; -EINTR. + */ + return regs->r10; + } + } +} + +/* Define some dummy arguments to be able to reach the regs argument. */ +int +sys_rt_sigsuspend(sigset_t *unewset, size_t sigsetsize, long r12, long r13, + long mof, long srp, struct pt_regs *regs) +{ + sigset_t saveset; + sigset_t newset; + + if (sigsetsize != sizeof(sigset_t)) + return -EINVAL; + + if (copy_from_user(&newset, unewset, sizeof(newset))) + return -EFAULT; + + sigdelsetmask(&newset, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + + saveset = current->blocked; + current->blocked = newset; + + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + regs->r10 = -EINTR; + + while (1) { + current->state = TASK_INTERRUPTIBLE; + schedule(); + + if (do_signal(0, &saveset, regs)) { + /* See comment in function above. */ + return regs->r10; + } + } +} + +int +sys_sigaction(int signal, const struct old_sigaction *act, + struct old_sigaction *oact) +{ + int retval; + struct k_sigaction newk; + struct k_sigaction oldk; + + if (act) { + old_sigset_t mask; + + if (!access_ok(VERIFY_READ, act, sizeof(*act)) || + __get_user(newk.sa.sa_handler, &act->sa_handler) || + __get_user(newk.sa.sa_restorer, &act->sa_restorer)) + return -EFAULT; + + __get_user(newk.sa.sa_flags, &act->sa_flags); + __get_user(mask, &act->sa_mask); + siginitset(&newk.sa.sa_mask, mask); + } + + retval = do_sigaction(signal, act ? &newk : NULL, oact ? &oldk : NULL); + + if (!retval && oact) { + if (!access_ok(VERIFY_WRITE, oact, sizeof(*oact)) || + __put_user(oldk.sa.sa_handler, &oact->sa_handler) || + __put_user(oldk.sa.sa_restorer, &oact->sa_restorer)) + return -EFAULT; + + __put_user(oldk.sa.sa_flags, &oact->sa_flags); + __put_user(oldk.sa.sa_mask.sig[0], &oact->sa_mask); + } + + return retval; +} + +int +sys_sigaltstack(const stack_t __user *uss, stack_t __user *uoss) +{ + return do_sigaltstack(uss, uoss, rdusp()); +} + +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_thread_info()->restart_block.fn = do_no_restart_syscall; + + /* + * Restore the registers from &sc->regs. sc is already checked + * for VERIFY_READ since the signal_frame was previously + * checked in sys_sigreturn(). + */ + if (__copy_from_user(regs, sc, sizeof(struct pt_regs))) + goto badframe; + + /* Make that the user-mode flag is set. */ + regs->ccs |= (1 << (U_CCS_BITNR + CCS_SHIFT)); + + /* Restore the old USP. */ + err |= __get_user(old_usp, &sc->usp); + wrusp(old_usp); + + return err; + +badframe: + return 1; +} + +/* Define some dummy arguments to be able to reach the regs argument. */ +asmlinkage int +sys_sigreturn(long r10, long r11, long r12, long r13, long mof, long srp, + struct pt_regs *regs) +{ + sigset_t set; + struct signal_frame __user *frame; + unsigned long oldspc = regs->spc; + unsigned long oldccs = regs->ccs; + + frame = (struct signal_frame *) rdusp(); + + /* + * Since the signal is stacked on a dword boundary, the frame + * should be dword aligned here as well. It it's not, then the + * user is trying some funny business. + */ + 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; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + + current->blocked = set; + + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->sc)) + goto badframe; + + keep_debug_flags(oldccs, oldspc, regs); + + return regs->r10; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* Define some dummy variables to be able to reach the regs argument. */ +asmlinkage int +sys_rt_sigreturn(long r10, long r11, long r12, long r13, long mof, long srp, + struct pt_regs *regs) +{ + sigset_t set; + struct rt_signal_frame __user *frame; + unsigned long oldspc = regs->spc; + unsigned long oldccs = regs->ccs; + + frame = (struct rt_signal_frame *) rdusp(); + + /* + * Since the signal is stacked on a dword boundary, the frame + * should be dword aligned here as well. It it's not, then the + * user is trying some funny business. + */ + 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; + + sigdelsetmask(&set, ~_BLOCKABLE); + spin_lock_irq(¤t->sighand->siglock); + + current->blocked = set; + + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + + if (restore_sigcontext(regs, &frame->uc.uc_mcontext)) + goto badframe; + + if (do_sigaltstack(&frame->uc.uc_stack, NULL, rdusp()) == -EFAULT) + goto badframe; + + keep_debug_flags(oldccs, oldspc, regs); + + return regs->r10; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +/* Setup a signal frame. */ +static int +setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs, + unsigned long mask) +{ + int err; + unsigned long usp; + + err = 0; + usp = rdusp(); + + /* + * Copy the registers. They are located first in sc, so it's + * possible to use sc directly. + */ + err |= __copy_to_user(sc, regs, sizeof(struct pt_regs)); + + err |= __put_user(mask, &sc->oldmask); + err |= __put_user(usp, &sc->usp); + + return err; +} + +/* Figure out where to put the new signal frame - usually on the stack. */ +static inline void __user * +get_sigframe(struct k_sigaction *ka, struct pt_regs * regs, size_t frame_size) +{ + unsigned long sp; + + sp = rdusp(); + + /* This is the X/Open sanctioned signal stack switching. */ + if (ka->sa.sa_flags & SA_ONSTACK) { + if (!on_sig_stack(sp)) + sp = current->sas_ss_sp + current->sas_ss_size; + } + + /* Make sure the frame is dword-aligned. */ + sp &= ~3; + + return (void __user *)(sp - frame_size); +} + +/* Grab and setup a signal frame. + * + * Basically a lot of state-info is stacked, and arranged for the + * user-mode program to return to the kernel using either a trampiline + * which performs the syscall sigreturn(), or a provided user-mode + * trampoline. + */ +static void +setup_frame(int sig, struct k_sigaction *ka, sigset_t *set, + struct pt_regs * regs) +{ + int err; + unsigned long return_ip; + struct signal_frame __user *frame; + + err = 0; + frame = get_sigframe(ka, regs, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + err |= setup_sigcontext(&frame->sc, regs, set->sig[0]); + + if (err) + goto give_sigsegv; + + if (_NSIG_WORDS > 1) { + err |= __copy_to_user(frame->extramask, &set->sig[1], + sizeof(frame->extramask)); + } + + if (err) + goto give_sigsegv; + + /* + * Set up to return from user-space. If provided, use a stub + * already located in user-space. + */ + if (ka->sa.sa_flags & SA_RESTORER) { + return_ip = (unsigned long)ka->sa.sa_restorer; + } else { + /* Trampoline - the desired return ip is in the signal return page. */ + return_ip = cris_signal_return_page; + + /* + * This is movu.w __NR_sigreturn, r9; break 13; + * + * WE DO NOT USE IT ANY MORE! It's only left here for historical + * reasons and because gdb uses it as a signature to notice + * signal handler stack frames. + */ + 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) + goto give_sigsegv; + + /* + * Set up registers for signal handler. + * + * Where the code enters now. + * Where the code enter later. + * First argument, signo. + */ + regs->erp = (unsigned long) ka->sa.sa_handler; + regs->srp = return_ip; + regs->r10 = sig; + + /* Actually move the USP to reflect the stacked frame. */ + wrusp((unsigned long)frame); + + return; + +give_sigsegv: + if (sig == SIGSEGV) + ka->sa.sa_handler = SIG_DFL; + + force_sig(SIGSEGV, current); +} + +static void +setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info, + sigset_t *set, struct pt_regs * regs) +{ + int err; + unsigned long return_ip; + struct rt_signal_frame __user *frame; + + err = 0; + frame = get_sigframe(ka, regs, sizeof(*frame)); + + if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) + goto give_sigsegv; + + /* TODO: what is the current->exec_domain stuff and invmap ? */ + + err |= __put_user(&frame->info, &frame->pinfo); + err |= __put_user(&frame->uc, &frame->puc); + err |= copy_siginfo_to_user(&frame->info, info); + + if (err) + goto give_sigsegv; + + /* 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)); + + if (err) + goto give_sigsegv; + + /* + * Set up to return from user-space. If provided, use a stub + * already located in user-space. + */ + if (ka->sa.sa_flags & SA_RESTORER) { + return_ip = (unsigned long) ka->sa.sa_restorer; + } else { + /* Trampoline - the desired return ip is in the signal return page. */ + return_ip = cris_signal_return_page + 6; + + /* + * This is movu.w __NR_rt_sigreturn, r9; break 13; + * + * WE DO NOT USE IT ANY MORE! It's only left here for historical + * reasons and because gdb uses it as a signature to notice + * signal handler stack frames. + */ + 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) + goto give_sigsegv; + + /* + * Set up registers for signal handler. + * + * Where the code enters now. + * Where the code enters later. + * First argument is signo. + * Second argument is (siginfo_t *). + * Third argument is unused. + */ + regs->erp = (unsigned long) ka->sa.sa_handler; + regs->srp = return_ip; + regs->r10 = sig; + regs->r11 = (unsigned long) &frame->info; + regs->r12 = 0; + + /* Actually move the usp to reflect the stacked frame. */ + wrusp((unsigned long)frame); + + return; + +give_sigsegv: + if (sig == SIGSEGV) + ka->sa.sa_handler = SIG_DFL; + + force_sig(SIGSEGV, current); +} + +/* Invoke a singal handler to, well, handle the signal. */ +extern inline void +handle_signal(int canrestart, unsigned long sig, + siginfo_t *info, struct k_sigaction *ka, + sigset_t *oldset, struct pt_regs * regs) +{ + /* Check if this got called from a system call. */ + if (canrestart) { + /* If so, check system call restarting. */ + switch (regs->r10) { + case -ERESTART_RESTARTBLOCK: + case -ERESTARTNOHAND: + /* + * This means that the syscall should + * only be restarted if there was no + * handler for the signal, and since + * this point isn't reached unless + * there is a handler, there's no need + * to restart. + */ + regs->r10 = -EINTR; + break; + + case -ERESTARTSYS: + /* + * This means restart the syscall if + * there is no handler, or the handler + * was registered with SA_RESTART. + */ + if (!(ka->sa.sa_flags & SA_RESTART)) { + regs->r10 = -EINTR; + break; + } + + /* Fall through. */ + + case -ERESTARTNOINTR: + /* + * This means that the syscall should + * be called again after the signal + * handler returns. + */ + RESTART_CRIS_SYS(regs); + break; + } + } + + /* Set up the stack frame. */ + if (ka->sa.sa_flags & SA_SIGINFO) + setup_rt_frame(sig, ka, info, oldset, regs); + else + setup_frame(sig, ka, oldset, regs); + + if (ka->sa.sa_flags & SA_ONESHOT) + ka->sa.sa_handler = SIG_DFL; + + if (!(ka->sa.sa_flags & SA_NODEFER)) { + spin_lock_irq(¤t->sighand->siglock); + sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask); + sigaddset(¤t->blocked,sig); + recalc_sigpending(); + spin_unlock_irq(¤t->sighand->siglock); + } +} + +/* + * Note that 'init' is a special process: it doesn't get signals it doesn't + * want to handle. Thus you cannot kill init even with a SIGKILL even by + * mistake. + * + * 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. + */ +int +do_signal(int canrestart, sigset_t *oldset, struct pt_regs *regs) +{ + int signr; + siginfo_t info; + struct k_sigaction ka; + + /* + * The common case should go fast, which is why this point is + * reached from kernel-mode. If that's the case, just return + * without doing anything. + */ + if (!user_mode(regs)) + return 1; + + if (!oldset) + oldset = ¤t->blocked; + + signr = get_signal_to_deliver(&info, &ka, regs, NULL); + + if (signr > 0) { + /* Deliver the signal. */ + handle_signal(canrestart, signr, &info, &ka, oldset, regs); + return 1; + } + + /* Got here 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->r10 = __NR_restart_syscall; + regs->erp -= 2; + } + } + + return 0; +} + +asmlinkage void +ugdb_trap_user(struct thread_info *ti, int sig) +{ + if (((user_regs(ti)->exs & 0xff00) >> 8) != SINGLE_STEP_INTR_VECT) { + /* Zero single-step PC if the reason we stopped wasn't a single + step exception. This is to avoid relying on it when it isn't + reliable. */ + user_regs(ti)->spc = 0; + } + /* FIXME: Filter out false h/w breakpoint hits (i.e. EDA + not withing any configured h/w breakpoint range). Synchronize with + what already exists for kernel debugging. */ + if (((user_regs(ti)->exs & 0xff00) >> 8) == BREAK_8_INTR_VECT) { + /* Break 8: subtract 2 from ERP unless in a delay slot. */ + if (!(user_regs(ti)->erp & 0x1)) + user_regs(ti)->erp -= 2; + } + sys_kill(ti->task->pid, sig); +} + +void +keep_debug_flags(unsigned long oldccs, unsigned long oldspc, + struct pt_regs *regs) +{ + if (oldccs & (1 << Q_CCS_BITNR)) { + /* Pending single step due to single-stepping the break 13 + in the signal trampoline: keep the Q flag. */ + regs->ccs |= (1 << Q_CCS_BITNR); + /* S flag should be set - complain if it's not. */ + if (!(oldccs & (1 << (S_CCS_BITNR + CCS_SHIFT)))) { + printk("Q flag but no S flag?"); + } + regs->ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT)); + /* Assume the SPC is valid and interesting. */ + regs->spc = oldspc; + + } else if (oldccs & (1 << (S_CCS_BITNR + CCS_SHIFT))) { + /* If a h/w bp was set in the signal handler we need + to keep the S flag. */ + regs->ccs |= (1 << (S_CCS_BITNR + CCS_SHIFT)); + /* Don't keep the old SPC though; if we got here due to + a single-step, the Q flag should have been set. */ + } else if (regs->spc) { + /* If we were single-stepping *before* the signal was taken, + we don't want to restore that state now, because GDB will + have forgotten all about it. */ + regs->spc = 0; + regs->ccs &= ~(1 << (S_CCS_BITNR + CCS_SHIFT)); + } +} + +/* Set up the trampolines on the signal return page. */ +int __init +cris_init_signal(void) +{ + u16* data = (u16*)kmalloc(PAGE_SIZE, GFP_KERNEL); + + /* This is movu.w __NR_sigreturn, r9; break 13; */ + data[0] = 0x9c5f; + data[1] = __NR_sigreturn; + data[2] = 0xe93d; + /* This is movu.w __NR_rt_sigreturn, r9; break 13; */ + data[3] = 0x9c5f; + data[4] = __NR_rt_sigreturn; + data[5] = 0xe93d; + + /* Map to userspace with appropriate permissions (no write access...) */ + cris_signal_return_page = (unsigned long) + __ioremap_prot(virt_to_phys(data), PAGE_SIZE, PAGE_SIGNAL_TRAMPOLINE); + + return 0; +} + +__initcall(cris_init_signal); diff --git a/arch/cris/arch-v32/kernel/smp.c b/arch/cris/arch-v32/kernel/smp.c new file mode 100644 index 0000000..2c5cae0 --- /dev/null +++ b/arch/cris/arch-v32/kernel/smp.c @@ -0,0 +1,348 @@ +#include <asm/delay.h> +#include <asm/arch/irq.h> +#include <asm/arch/hwregs/intr_vect.h> +#include <asm/arch/hwregs/intr_vect_defs.h> +#include <asm/tlbflush.h> +#include <asm/mmu_context.h> +#include <asm/arch/hwregs/mmu_defs_asm.h> +#include <asm/arch/hwregs/supp_reg.h> +#include <asm/atomic.h> + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/timex.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/cpumask.h> +#include <linux/interrupt.h> + +#define IPI_SCHEDULE 1 +#define IPI_CALL 2 +#define IPI_FLUSH_TLB 4 + +#define FLUSH_ALL (void*)0xffffffff + +/* Vector of locks used for various atomic operations */ +spinlock_t cris_atomic_locks[] = { [0 ... LOCK_COUNT - 1] = SPIN_LOCK_UNLOCKED}; + +/* CPU masks */ +cpumask_t cpu_online_map = CPU_MASK_NONE; +cpumask_t phys_cpu_present_map = CPU_MASK_NONE; + +/* Variables used during SMP boot */ +volatile int cpu_now_booting = 0; +volatile struct thread_info *smp_init_current_idle_thread; + +/* Variables used during IPI */ +static DEFINE_SPINLOCK(call_lock); +static DEFINE_SPINLOCK(tlbstate_lock); + +struct call_data_struct { + void (*func) (void *info); + void *info; + int wait; +}; + +static struct call_data_struct * call_data; + +static struct mm_struct* flush_mm; +static struct vm_area_struct* flush_vma; +static unsigned long flush_addr; + +extern int setup_irq(int, struct irqaction *); + +/* Mode registers */ +static unsigned long irq_regs[NR_CPUS] = +{ + regi_irq, + regi_irq2 +}; + +static irqreturn_t crisv32_ipi_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static int send_ipi(int vector, int wait, cpumask_t cpu_mask); +static struct irqaction irq_ipi = { crisv32_ipi_interrupt, SA_INTERRUPT, + CPU_MASK_NONE, "ipi", NULL, NULL}; + +extern void cris_mmu_init(void); +extern void cris_timer_init(void); + +/* SMP initialization */ +void __init smp_prepare_cpus(unsigned int max_cpus) +{ + int i; + + /* From now on we can expect IPIs so set them up */ + setup_irq(IPI_INTR_VECT, &irq_ipi); + + /* Mark all possible CPUs as present */ + for (i = 0; i < max_cpus; i++) + cpu_set(i, phys_cpu_present_map); +} + +void __devinit smp_prepare_boot_cpu(void) +{ + /* PGD pointer has moved after per_cpu initialization so + * update the MMU. + */ + pgd_t **pgd; + pgd = (pgd_t**)&per_cpu(current_pgd, smp_processor_id()); + + SUPP_BANK_SEL(1); + SUPP_REG_WR(RW_MM_TLB_PGD, pgd); + SUPP_BANK_SEL(2); + SUPP_REG_WR(RW_MM_TLB_PGD, pgd); + + cpu_set(0, cpu_online_map); + cpu_set(0, phys_cpu_present_map); +} + +void __init smp_cpus_done(unsigned int max_cpus) +{ +} + +/* Bring one cpu online.*/ +static int __init +smp_boot_one_cpu(int cpuid) +{ + unsigned timeout; + struct task_struct *idle; + + idle = fork_idle(cpuid); + if (IS_ERR(idle)) + panic("SMP: fork failed for CPU:%d", cpuid); + + idle->thread_info->cpu = cpuid; + + /* Information to the CPU that is about to boot */ + smp_init_current_idle_thread = idle->thread_info; + cpu_now_booting = cpuid; + + /* Wait for CPU to come online */ + for (timeout = 0; timeout < 10000; timeout++) { + if(cpu_online(cpuid)) { + cpu_now_booting = 0; + smp_init_current_idle_thread = NULL; + return 0; /* CPU online */ + } + udelay(100); + barrier(); + } + + put_task_struct(idle); + idle = NULL; + + printk(KERN_CRIT "SMP: CPU:%d is stuck.\n", cpuid); + return -1; +} + +/* Secondary CPUs starts uing C here. Here we need to setup CPU + * specific stuff such as the local timer and the MMU. */ +void __init smp_callin(void) +{ + extern void cpu_idle(void); + + int cpu = cpu_now_booting; + reg_intr_vect_rw_mask vect_mask = {0}; + + /* Initialise the idle task for this CPU */ + atomic_inc(&init_mm.mm_count); + current->active_mm = &init_mm; + + /* Set up MMU */ + cris_mmu_init(); + __flush_tlb_all(); + + /* Setup local timer. */ + cris_timer_init(); + + /* Enable IRQ and idle */ + REG_WR(intr_vect, irq_regs[cpu], rw_mask, vect_mask); + unmask_irq(IPI_INTR_VECT); + unmask_irq(TIMER_INTR_VECT); + local_irq_enable(); + + cpu_set(cpu, cpu_online_map); + cpu_idle(); +} + +/* Stop execution on this CPU.*/ +void stop_this_cpu(void* dummy) +{ + local_irq_disable(); + asm volatile("halt"); +} + +/* Other calls */ +void smp_send_stop(void) +{ + smp_call_function(stop_this_cpu, NULL, 1, 0); +} + +int setup_profiling_timer(unsigned int multiplier) +{ + return -EINVAL; +} + + +/* cache_decay_ticks is used by the scheduler to decide if a process + * is "hot" on one CPU. A higher value means a higher penalty to move + * a process to another CPU. Our cache is rather small so we report + * 1 tick. + */ +unsigned long cache_decay_ticks = 1; + +int __devinit __cpu_up(unsigned int cpu) +{ + smp_boot_one_cpu(cpu); + return cpu_online(cpu) ? 0 : -ENOSYS; +} + +void smp_send_reschedule(int cpu) +{ + cpumask_t cpu_mask = CPU_MASK_NONE; + cpu_set(cpu, cpu_mask); + send_ipi(IPI_SCHEDULE, 0, cpu_mask); +} + +/* TLB flushing + * + * Flush needs to be done on the local CPU and on any other CPU that + * may have the same mapping. The mm->cpu_vm_mask is used to keep track + * of which CPUs that a specific process has been executed on. + */ +void flush_tlb_common(struct mm_struct* mm, struct vm_area_struct* vma, unsigned long addr) +{ + unsigned long flags; + cpumask_t cpu_mask; + + spin_lock_irqsave(&tlbstate_lock, flags); + cpu_mask = (mm == FLUSH_ALL ? CPU_MASK_ALL : mm->cpu_vm_mask); + cpu_clear(smp_processor_id(), cpu_mask); + flush_mm = mm; + flush_vma = vma; + flush_addr = addr; + send_ipi(IPI_FLUSH_TLB, 1, cpu_mask); + spin_unlock_irqrestore(&tlbstate_lock, flags); +} + +void flush_tlb_all(void) +{ + __flush_tlb_all(); + flush_tlb_common(FLUSH_ALL, FLUSH_ALL, 0); +} + +void flush_tlb_mm(struct mm_struct *mm) +{ + __flush_tlb_mm(mm); + flush_tlb_common(mm, FLUSH_ALL, 0); + /* No more mappings in other CPUs */ + cpus_clear(mm->cpu_vm_mask); + cpu_set(smp_processor_id(), mm->cpu_vm_mask); +} + +void flush_tlb_page(struct vm_area_struct *vma, + unsigned long addr) +{ + __flush_tlb_page(vma, addr); + flush_tlb_common(vma->vm_mm, vma, addr); +} + +/* Inter processor interrupts + * + * The IPIs are used for: + * * Force a schedule on a CPU + * * FLush TLB on other CPUs + * * Call a function on other CPUs + */ + +int send_ipi(int vector, int wait, cpumask_t cpu_mask) +{ + int i = 0; + reg_intr_vect_rw_ipi ipi = REG_RD(intr_vect, irq_regs[i], rw_ipi); + int ret = 0; + + /* Calculate CPUs to send to. */ + cpus_and(cpu_mask, cpu_mask, cpu_online_map); + + /* Send the IPI. */ + for_each_cpu_mask(i, cpu_mask) + { + ipi.vector |= vector; + REG_WR(intr_vect, irq_regs[i], rw_ipi, ipi); + } + + /* Wait for IPI to finish on other CPUS */ + if (wait) { + for_each_cpu_mask(i, cpu_mask) { + int j; + for (j = 0 ; j < 1000; j++) { + ipi = REG_RD(intr_vect, irq_regs[i], rw_ipi); + if (!ipi.vector) + break; + udelay(100); + } + + /* Timeout? */ + if (ipi.vector) { + printk("SMP call timeout from %d to %d\n", smp_processor_id(), i); + ret = -ETIMEDOUT; + dump_stack(); + } + } + } + return ret; +} + +/* + * You must not call this function with disabled interrupts or from a + * hardware interrupt handler or from a bottom half handler. + */ +int smp_call_function(void (*func)(void *info), void *info, + int nonatomic, int wait) +{ + cpumask_t cpu_mask = CPU_MASK_ALL; + struct call_data_struct data; + int ret; + + cpu_clear(smp_processor_id(), cpu_mask); + + WARN_ON(irqs_disabled()); + + data.func = func; + data.info = info; + data.wait = wait; + + spin_lock(&call_lock); + call_data = &data; + ret = send_ipi(IPI_CALL, wait, cpu_mask); + spin_unlock(&call_lock); + + return ret; +} + +irqreturn_t crisv32_ipi_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + void (*func) (void *info) = call_data->func; + void *info = call_data->info; + reg_intr_vect_rw_ipi ipi; + + ipi = REG_RD(intr_vect, irq_regs[smp_processor_id()], rw_ipi); + + if (ipi.vector & IPI_CALL) { + func(info); + } + if (ipi.vector & IPI_FLUSH_TLB) { + if (flush_mm == FLUSH_ALL) + __flush_tlb_all(); + else if (flush_vma == FLUSH_ALL) + __flush_tlb_mm(flush_mm); + else + __flush_tlb_page(flush_vma, flush_addr); + } + + ipi.vector = 0; + REG_WR(intr_vect, irq_regs[smp_processor_id()], rw_ipi, ipi); + + return IRQ_HANDLED; +} + diff --git a/arch/cris/arch-v32/kernel/time.c b/arch/cris/arch-v32/kernel/time.c new file mode 100644 index 0000000..d48e397 --- /dev/null +++ b/arch/cris/arch-v32/kernel/time.c @@ -0,0 +1,341 @@ +/* $Id: time.c,v 1.19 2005/04/29 05:40:09 starvik Exp $ + * + * linux/arch/cris/arch-v32/kernel/time.c + * + * Copyright (C) 2003 Axis Communications AB + * + */ + +#include <linux/config.h> +#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/threads.h> +#include <asm/types.h> +#include <asm/signal.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/rtc.h> +#include <asm/irq.h> + +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/timer_defs.h> +#include <asm/arch/hwregs/intr_vect_defs.h> + +/* Watchdog defines */ +#define ETRAX_WD_KEY_MASK 0x7F /* key is 7 bit */ +#define ETRAX_WD_HZ 763 /* watchdog counts at 763 Hz */ +#define ETRAX_WD_CNT ((2*ETRAX_WD_HZ)/HZ + 1) /* Number of 763 counts before watchdog bites */ + +unsigned long timer_regs[NR_CPUS] = +{ + regi_timer, +#ifdef CONFIG_SMP + regi_timer2 +#endif +}; + +extern void update_xtime_from_cmos(void); +extern int set_rtc_mmss(unsigned long nowtime); +extern int setup_irq(int, struct irqaction *); +extern int have_rtc; + +unsigned long get_ns_in_jiffie(void) +{ + reg_timer_r_tmr0_data data; + unsigned long ns; + + data = REG_RD(timer, regi_timer, r_tmr0_data); + ns = (TIMER0_DIV - data) * 10; + return ns; +} + +unsigned long do_slow_gettimeoffset(void) +{ + unsigned long count; + unsigned long usec_count = 0; + + static unsigned long count_p = TIMER0_DIV;/* for the first call after boot */ + static unsigned long jiffies_p = 0; + + /* + * cache volatile jiffies temporarily; we have IRQs turned off. + */ + unsigned long jiffies_t; + + /* 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 = REG_RD(timer, regi_timer, r_tmr0_data); + jiffies_t = jiffies; + + /* + * avoiding timer inconsistencies (they are rare, but they happen)... + * there are one problem that must be avoided here: + * 1. the timer counter underflows + */ + if( jiffies_t == jiffies_p ) { + if( count > count_p ) { + /* Timer wrapped, use new count and prescale + * increase the time corresponding to one jiffie + */ + usec_count = 1000000/HZ; + } + } else + jiffies_p = jiffies_t; + count_p = count; + /* Convert timer value to usec */ + /* 100 MHz timer, divide by 100 to get usec */ + usec_count += (TIMER0_DIV - count) / 100; + return usec_count; +} + +/* From timer MDS describing the hardware watchdog: + * 4.3.1 Watchdog Operation + * The watchdog timer is an 8-bit timer with a configurable start value. + * Once started the whatchdog counts downwards with a frequency of 763 Hz + * (100/131072 MHz). When the watchdog counts down to 1, it generates an + * NMI (Non Maskable Interrupt), and when it counts down to 0, it resets the + * chip. + */ +/* This gives us 1.3 ms to do something useful when the NMI comes */ + +/* right now, starting the watchdog is the same as resetting it */ +#define start_watchdog reset_watchdog + +#if defined(CONFIG_ETRAX_WATCHDOG) +static short int watchdog_key = 42; /* arbitrary 7 bit 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) + reg_timer_rw_wd_ctrl wd_ctrl = { 0 }; + + /* 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 ^= ETRAX_WD_KEY_MASK; /* invert key, which is 7 bits */ + wd_ctrl.cnt = ETRAX_WD_CNT; + wd_ctrl.cmd = regk_timer_start; + wd_ctrl.key = watchdog_key; + REG_WR(timer, regi_timer, rw_wd_ctrl, wd_ctrl); + } +#endif +} + +/* stop the watchdog - we still need the correct key */ + +void +stop_watchdog(void) +{ +#if defined(CONFIG_ETRAX_WATCHDOG) + reg_timer_rw_wd_ctrl wd_ctrl = { 0 }; + watchdog_key ^= ETRAX_WD_KEY_MASK; /* invert key, which is 7 bits */ + wd_ctrl.cnt = ETRAX_WD_CNT; + wd_ctrl.cmd = regk_timer_stop; + wd_ctrl.key = watchdog_key; + REG_WR(timer, regi_timer, rw_wd_ctrl, wd_ctrl); +#endif +} + +extern void show_registers(struct pt_regs *regs); + +void +handle_watchdog_bite(struct pt_regs* regs) +{ +#if defined(CONFIG_ETRAX_WATCHDOG) + extern int cause_of_death; + + raw_printk("Watchdog bite\n"); + + /* Check if forced restart or unexpected watchdog */ + if (cause_of_death == 0xbedead) { + while(1); + } + + /* Unexpected watchdog, stop the watchdog and dump registers*/ + stop_watchdog(); + raw_printk("Oops: bitten by watchdog\n"); + show_registers(regs); +#ifndef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY + reset_watchdog(); +#endif + while(1) /* nothing */; +#endif +} + +/* last time the cmos clock got updated */ +static long last_rtc_update = 0; + +/* + * timer_interrupt() needs to keep up the real-time clock, + * as well as call the "do_timer()" routine every clocktick + */ + +//static unsigned short myjiff; /* used by our debug routine print_timestamp */ + +extern void cris_do_profile(struct pt_regs *regs); + +static inline irqreturn_t +timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + reg_timer_r_masked_intr masked_intr; + reg_timer_rw_ack_intr ack_intr = { 0 }; + + /* Check if the timer interrupt is for us (a tmr0 int) */ + masked_intr = REG_RD(timer, timer_regs[cpu], r_masked_intr); + if (!masked_intr.tmr0) + return IRQ_NONE; + + /* acknowledge the timer irq */ + ack_intr.tmr0 = 1; + REG_WR(timer, timer_regs[cpu], rw_ack_intr, ack_intr); + + /* reset watchdog otherwise it resets us! */ + reset_watchdog(); + + /* Update statistics. */ + update_process_times(user_mode(regs)); + + cris_do_profile(regs); /* Save profiling information */ + + /* The master CPU is responsible for the time keeping. */ + if (cpu != 0) + return IRQ_HANDLED; + + /* call the real timer interrupt handler */ + do_timer(regs); + + /* + * If we have an externally synchronized Linux clock, then update + * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be + * called as close as possible to 500 ms before the new second starts. + * + * The division here is not time critical since it will run once in + * 11 minutes + */ + if ((time_status & STA_UNSYNC) == 0 && + xtime.tv_sec > last_rtc_update + 660 && + (xtime.tv_nsec / 1000) >= 500000 - (tick_nsec / 1000) / 2 && + (xtime.tv_nsec / 1000) <= 500000 + (tick_nsec / 1000) / 2) { + if (set_rtc_mmss(xtime.tv_sec) == 0) + last_rtc_update = xtime.tv_sec; + else + last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */ + } + return IRQ_HANDLED; +} + +/* timer is SA_SHIRQ so drivers can add stuff to the timer irq chain + * it needs to be SA_INTERRUPT to make the jiffies update work properly + */ + +static struct irqaction irq_timer = { timer_interrupt, SA_SHIRQ | SA_INTERRUPT, + CPU_MASK_NONE, "timer", NULL, NULL}; + +void __init +cris_timer_init(void) +{ + int cpu = smp_processor_id(); + reg_timer_rw_tmr0_ctrl tmr0_ctrl = { 0 }; + reg_timer_rw_tmr0_div tmr0_div = TIMER0_DIV; + reg_timer_rw_intr_mask timer_intr_mask; + + /* Setup the etrax timers + * Base frequency is 100MHz, divider 1000000 -> 100 HZ + * We use timer0, so timer1 is free. + * The trig timer is used by the fasttimer API if enabled. + */ + + tmr0_ctrl.op = regk_timer_ld; + tmr0_ctrl.freq = regk_timer_f100; + REG_WR(timer, timer_regs[cpu], rw_tmr0_div, tmr0_div); + REG_WR(timer, timer_regs[cpu], rw_tmr0_ctrl, tmr0_ctrl); /* Load */ + tmr0_ctrl.op = regk_timer_run; + REG_WR(timer, timer_regs[cpu], rw_tmr0_ctrl, tmr0_ctrl); /* Start */ + + /* enable the timer irq */ + timer_intr_mask = REG_RD(timer, timer_regs[cpu], rw_intr_mask); + timer_intr_mask.tmr0 = 1; + REG_WR(timer, timer_regs[cpu], rw_intr_mask, timer_intr_mask); +} + +void __init +time_init(void) +{ + reg_intr_vect_rw_mask intr_mask; + + /* 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; + + if(RTC_INIT() < 0) { + /* no RTC, start at 1980 */ + xtime.tv_sec = 0; + xtime.tv_nsec = 0; + have_rtc = 0; + } else { + /* get the current time */ + have_rtc = 1; + update_xtime_from_cmos(); + } + + /* + * Initialize wall_to_monotonic such that adding it to xtime will yield zero, the + * tv_nsec field must be normalized (i.e., 0 <= nsec < NSEC_PER_SEC). + */ + set_normalized_timespec(&wall_to_monotonic, -xtime.tv_sec, -xtime.tv_nsec); + + /* Start CPU local timer */ + cris_timer_init(); + + /* enable the timer irq in global config */ + intr_mask = REG_RD(intr_vect, regi_irq, rw_mask); + intr_mask.timer = 1; + REG_WR(intr_vect, regi_irq, rw_mask, intr_mask); + + /* now actually register the timer irq handler that calls timer_interrupt() */ + + setup_irq(TIMER_INTR_VECT, &irq_timer); + + /* 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. */ + { + unsigned long flags; + local_save_flags(flags); + flags |= (1<<30); /* NMI M flag is at bit 30 */ + local_irq_restore(flags); + } +#endif +} diff --git a/arch/cris/arch-v32/kernel/traps.c b/arch/cris/arch-v32/kernel/traps.c new file mode 100644 index 0000000..6e37870 --- /dev/null +++ b/arch/cris/arch-v32/kernel/traps.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2003, Axis Communications AB. + */ + +#include <linux/config.h> +#include <linux/ptrace.h> +#include <asm/uaccess.h> + +#include <asm/arch/hwregs/supp_reg.h> + +extern void reset_watchdog(void); +extern void stop_watchdog(void); + +extern int raw_printk(const char *fmt, ...); + +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 proccess for all cases this + * function is called, and current->thread.usp isn't up to date for the + * current proccess. Experience shows that using USP is the way to go. + */ + unsigned long usp; + unsigned long d_mmu_cause; + unsigned long i_mmu_cause; + + usp = rdusp(); + + raw_printk("CPU: %d\n", smp_processor_id()); + + raw_printk("ERP: %08lx SRP: %08lx CCS: %08lx USP: %08lx MOF: %08lx\n", + regs->erp, regs->srp, regs->ccs, usp, regs->mof); + + raw_printk(" r0: %08lx r1: %08lx r2: %08lx r3: %08lx\n", + regs->r0, regs->r1, regs->r2, regs->r3); + + raw_printk(" r4: %08lx r5: %08lx r6: %08lx r7: %08lx\n", + regs->r4, regs->r5, regs->r6, regs->r7); + + raw_printk(" r8: %08lx r9: %08lx r10: %08lx r11: %08lx\n", + regs->r8, regs->r9, regs->r10, regs->r11); + + raw_printk("r12: %08lx r13: %08lx oR10: %08lx acr: %08lx\n", + regs->r12, regs->r13, regs->orig_r10, regs->acr); + + raw_printk("sp: %08lx\n", regs); + + SUPP_BANK_SEL(BANK_IM); + SUPP_REG_RD(RW_MM_CAUSE, i_mmu_cause); + + SUPP_BANK_SEL(BANK_DM); + SUPP_REG_RD(RW_MM_CAUSE, d_mmu_cause); + + raw_printk(" Data MMU Cause: %08lx\n", d_mmu_cause); + raw_printk("Instruction MMU Cause: %08lx\n", i_mmu_cause); + + raw_printk("Process %s (pid: %d, stackpage: %08lx)\n", + current->comm, current->pid, (unsigned long) current); + + /* Show additional info if in kernel-mode. */ + if (!user_mode(regs)) { + int i; + unsigned char c; + + 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); + + raw_printk("\nCode: "); + + if (regs->erp < PAGE_OFFSET) + goto bad_value; + + /* + * Quite often the value at regs->erp 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->erp + * location is pointed out in a ksymoops-friendly way by + * wrapping the byte for that address in parenthesis. + */ + for (i = -12; i < 12; i++) { + if (__get_user(c, &((unsigned char *) regs->erp)[i])) { +bad_value: + raw_printk(" Bad IP value."); + break; + } + + if (i == 0) + raw_printk("(%02x) ", c); + else + raw_printk("%02x ", c); + } + + raw_printk("\n"); + } +} + +/* + * This gets called from entry.S when the watchdog has bitten. Show something + * similiar to an Oops dump, and if the kernel if configured to be a nice doggy; + * halt instead of reboot. + */ +void +watchdog_bite_hook(struct pt_regs *regs) +{ +#ifdef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY + local_irq_disable(); + stop_watchdog(); + show_registers(regs); + + while (1) + ; /* Do nothing. */ +#else + show_registers(regs); +#endif +} + +/* This is normally the Oops function. */ +void +die_if_kernel(const char *str, struct pt_regs *regs, long err) +{ + if (user_mode(regs)) + return; + +#ifdef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY + /* + * This printout might take too long and could trigger + * the watchdog normally. If NICE_DOGGY is set, simply + * stop the watchdog during the printout. + */ + stop_watchdog(); +#endif + + raw_printk("%s: %04lx\n", str, err & 0xffff); + + show_registers(regs); + +#ifdef CONFIG_ETRAX_WATCHDOG_NICE_DOGGY + reset_watchdog(); +#endif + + do_exit(SIGSEGV); +} + +void arch_enable_nmi(void) +{ + unsigned long flags; + local_save_flags(flags); + flags |= (1<<30); /* NMI M flag is at bit 30 */ + local_irq_restore(flags); +} diff --git a/arch/cris/arch-v32/kernel/vcs_hook.c b/arch/cris/arch-v32/kernel/vcs_hook.c new file mode 100644 index 0000000..64d71c5 --- /dev/null +++ b/arch/cris/arch-v32/kernel/vcs_hook.c @@ -0,0 +1,96 @@ +// $Id: vcs_hook.c,v 1.2 2003/08/12 12:01:06 starvik Exp $ +// +// Call simulator hook. This is the part running in the +// simulated program. +// + +#include "vcs_hook.h" +#include <stdarg.h> +#include <asm/arch-v32/hwregs/reg_map.h> +#include <asm/arch-v32/hwregs/intr_vect_defs.h> + +#define HOOK_TRIG_ADDR 0xb7000000 /* hook cvlog model reg address */ +#define HOOK_MEM_BASE_ADDR 0xa0000000 /* csp4 (shared mem) base addr */ + +#define HOOK_DATA(offset) ((unsigned*) HOOK_MEM_BASE_ADDR)[offset] +#define VHOOK_DATA(offset) ((volatile unsigned*) HOOK_MEM_BASE_ADDR)[offset] +#define HOOK_TRIG(funcid) do { *((unsigned *) HOOK_TRIG_ADDR) = funcid; } while(0) +#define HOOK_DATA_BYTE(offset) ((unsigned char*) HOOK_MEM_BASE_ADDR)[offset] + + +// ------------------------------------------------------------------ hook_call +int hook_call( unsigned id, unsigned pcnt, ...) { + va_list ap; + unsigned i; + unsigned ret; +#ifdef USING_SOS + PREEMPT_OFF_SAVE(); +#endif + + // pass parameters + HOOK_DATA(0) = id; + + /* Have to make hook_print_str a special case since we call with a + parameter of byte type. Should perhaps be a separate + hook_call. */ + + if (id == hook_print_str) { + int i; + char *str; + + HOOK_DATA(1) = pcnt; + + va_start(ap, pcnt); + str = (char*)va_arg(ap,unsigned); + + for (i=0; i!=pcnt; i++) { + HOOK_DATA_BYTE(8+i) = str[i]; + } + HOOK_DATA_BYTE(8+i) = 0; /* null byte */ + } + else { + va_start(ap, pcnt); + for( i = 1; i <= pcnt; i++ ) HOOK_DATA(i) = va_arg(ap,unsigned); + va_end(ap); + } + + // read from mem to make sure data has propagated to memory before trigging + *((volatile unsigned*) HOOK_MEM_BASE_ADDR); + + // trigger hook + HOOK_TRIG(id); + + // wait for call to finish + while( VHOOK_DATA(0) > 0 ) {} + + // extract return value + + ret = VHOOK_DATA(1); + +#ifdef USING_SOS + PREEMPT_RESTORE(); +#endif + return ret; +} + +unsigned +hook_buf(unsigned i) +{ + return (HOOK_DATA(i)); +} + +void print_str( const char *str ) { + int i; + for (i=1; str[i]; i++); /* find null at end of string */ + hook_call(hook_print_str, i, str); +} + +// --------------------------------------------------------------- CPU_KICK_DOG +void CPU_KICK_DOG(void) { + (void) hook_call( hook_kick_dog, 0 ); +} + +// ------------------------------------------------------- CPU_WATCHDOG_TIMEOUT +void CPU_WATCHDOG_TIMEOUT( unsigned t ) { + (void) hook_call( hook_dog_timeout, 1, t ); +} diff --git a/arch/cris/arch-v32/kernel/vcs_hook.h b/arch/cris/arch-v32/kernel/vcs_hook.h new file mode 100644 index 0000000..7d73709 --- /dev/null +++ b/arch/cris/arch-v32/kernel/vcs_hook.h @@ -0,0 +1,42 @@ +// $Id: vcs_hook.h,v 1.1 2003/08/12 12:01:06 starvik Exp $ +// +// Call simulator hook functions + +#ifndef HOOK_H +#define HOOK_H + +int hook_call( unsigned id, unsigned pcnt, ...); + +enum hook_ids { + hook_debug_on = 1, + hook_debug_off, + hook_stop_sim_ok, + hook_stop_sim_fail, + hook_alloc_shared, + hook_ptr_shared, + hook_free_shared, + hook_file2shared, + hook_cmp_shared, + hook_print_params, + hook_sim_time, + hook_stop_sim, + hook_kick_dog, + hook_dog_timeout, + hook_rand, + hook_srand, + hook_rand_range, + hook_print_str, + hook_print_hex, + hook_cmp_offset_shared, + hook_fill_random_shared, + hook_alloc_random_data, + hook_calloc_random_data, + hook_print_int, + hook_print_uint, + hook_fputc, + hook_init_fd, + hook_sbrk + +}; + +#endif |