alistair23-linux/drivers/serial/dz.c
David Howells 7d12e780e0 IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.

The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around.  On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).

Where appropriate, an arch may override the generic storage facility and do
something different with the variable.  On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.

Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions.  Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller.  A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.

I've build this code with allyesconfig for x86_64 and i386.  I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.

This will affect all archs.  Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:

	struct pt_regs *old_regs = set_irq_regs(regs);

And put the old one back at the end:

	set_irq_regs(old_regs);

Don't pass regs through to generic_handle_irq() or __do_IRQ().

In timer_interrupt(), this sort of change will be necessary:

	-	update_process_times(user_mode(regs));
	-	profile_tick(CPU_PROFILING, regs);
	+	update_process_times(user_mode(get_irq_regs()));
	+	profile_tick(CPU_PROFILING);

I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().

Some notes on the interrupt handling in the drivers:

 (*) input_dev() is now gone entirely.  The regs pointer is no longer stored in
     the input_dev struct.

 (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking.  It does
     something different depending on whether it's been supplied with a regs
     pointer or not.

 (*) Various IRQ handler function pointers have been moved to type
     irq_handler_t.

Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:10:12 +01:00

815 lines
19 KiB
C

/*
* dz.c: Serial port driver for DECStations equiped
* with the DZ chipset.
*
* Copyright (C) 1998 Olivier A. D. Lebaillif
*
* Email: olivier.lebaillif@ifrsys.com
*
* [31-AUG-98] triemer
* Changed IRQ to use Harald's dec internals interrupts.h
* removed base_addr code - moving address assignment to setup.c
* Changed name of dz_init to rs_init to be consistent with tc code
* [13-NOV-98] triemer fixed code to receive characters
* after patches by harald to irq code.
* [09-JAN-99] triemer minor fix for schedule - due to removal of timeout
* field from "current" - somewhere between 2.1.121 and 2.1.131
Qua Jun 27 15:02:26 BRT 2001
* [27-JUN-2001] Arnaldo Carvalho de Melo <acme@conectiva.com.br> - cleanups
*
* Parts (C) 1999 David Airlie, airlied@linux.ie
* [07-SEP-99] Bugfixes
*
* [06-Jan-2002] Russell King <rmk@arm.linux.org.uk>
* Converted to new serial core
*/
#undef DEBUG_DZ
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <asm/bootinfo.h>
#include <asm/dec/interrupts.h>
#include <asm/dec/kn01.h>
#include <asm/dec/kn02.h>
#include <asm/dec/machtype.h>
#include <asm/dec/prom.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define CONSOLE_LINE (3) /* for definition of struct console */
#include "dz.h"
#define DZ_INTR_DEBUG 1
static char *dz_name = "DECstation DZ serial driver version ";
static char *dz_version = "1.02";
struct dz_port {
struct uart_port port;
unsigned int cflag;
};
static struct dz_port dz_ports[DZ_NB_PORT];
#ifdef DEBUG_DZ
/*
* debugging code to send out chars via prom
*/
static void debug_console(const char *s, int count)
{
unsigned i;
for (i = 0; i < count; i++) {
if (*s == 10)
prom_printf("%c", 13);
prom_printf("%c", *s++);
}
}
#endif
/*
* ------------------------------------------------------------
* dz_in () and dz_out ()
*
* These routines are used to access the registers of the DZ
* chip, hiding relocation differences between implementation.
* ------------------------------------------------------------
*/
static inline unsigned short dz_in(struct dz_port *dport, unsigned offset)
{
volatile unsigned short *addr =
(volatile unsigned short *) (dport->port.membase + offset);
return *addr;
}
static inline void dz_out(struct dz_port *dport, unsigned offset,
unsigned short value)
{
volatile unsigned short *addr =
(volatile unsigned short *) (dport->port.membase + offset);
*addr = value;
}
/*
* ------------------------------------------------------------
* rs_stop () and rs_start ()
*
* These routines are called before setting or resetting
* tty->stopped. They enable or disable transmitter interrupts,
* as necessary.
* ------------------------------------------------------------
*/
static void dz_stop_tx(struct uart_port *uport)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned short tmp, mask = 1 << dport->port.line;
unsigned long flags;
spin_lock_irqsave(&dport->port.lock, flags);
tmp = dz_in(dport, DZ_TCR); /* read the TX flag */
tmp &= ~mask; /* clear the TX flag */
dz_out(dport, DZ_TCR, tmp);
spin_unlock_irqrestore(&dport->port.lock, flags);
}
static void dz_start_tx(struct uart_port *uport)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned short tmp, mask = 1 << dport->port.line;
unsigned long flags;
spin_lock_irqsave(&dport->port.lock, flags);
tmp = dz_in(dport, DZ_TCR); /* read the TX flag */
tmp |= mask; /* set the TX flag */
dz_out(dport, DZ_TCR, tmp);
spin_unlock_irqrestore(&dport->port.lock, flags);
}
static void dz_stop_rx(struct uart_port *uport)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned long flags;
spin_lock_irqsave(&dport->port.lock, flags);
dport->cflag &= ~DZ_CREAD;
dz_out(dport, DZ_LPR, dport->cflag);
spin_unlock_irqrestore(&dport->port.lock, flags);
}
static void dz_enable_ms(struct uart_port *port)
{
/* nothing to do */
}
/*
* ------------------------------------------------------------
* Here starts the interrupt handling routines. All of the
* following subroutines are declared as inline and are folded
* into dz_interrupt. They were separated out for readability's
* sake.
*
* Note: rs_interrupt() is a "fast" interrupt, which means that it
* runs with interrupts turned off. People who may want to modify
* rs_interrupt() should try to keep the interrupt handler as fast as
* possible. After you are done making modifications, it is not a bad
* idea to do:
*
* make drivers/serial/dz.s
*
* and look at the resulting assemble code in dz.s.
*
* ------------------------------------------------------------
*/
/*
* ------------------------------------------------------------
* receive_char ()
*
* This routine deals with inputs from any lines.
* ------------------------------------------------------------
*/
static inline void dz_receive_chars(struct dz_port *dport)
{
struct tty_struct *tty = NULL;
struct uart_icount *icount;
int ignore = 0;
unsigned short status, tmp;
unsigned char ch, flag;
/* this code is going to be a problem...
the call to tty_flip_buffer is going to need
to be rethought...
*/
do {
status = dz_in(dport, DZ_RBUF);
/* punt so we don't get duplicate characters */
if (!(status & DZ_DVAL))
goto ignore_char;
ch = UCHAR(status); /* grab the char */
flag = TTY_NORMAL;
#if 0
if (info->is_console) {
if (ch == 0)
return; /* it's a break ... */
}
#endif
tty = dport->port.info->tty;/* now tty points to the proper dev */
icount = &dport->port.icount;
if (!tty)
break;
icount->rx++;
/* keep track of the statistics */
if (status & (DZ_OERR | DZ_FERR | DZ_PERR)) {
if (status & DZ_PERR) /* parity error */
icount->parity++;
else if (status & DZ_FERR) /* frame error */
icount->frame++;
if (status & DZ_OERR) /* overrun error */
icount->overrun++;
/* check to see if we should ignore the character
and mask off conditions that should be ignored
*/
if (status & dport->port.ignore_status_mask) {
if (++ignore > 100)
break;
goto ignore_char;
}
/* mask off the error conditions we want to ignore */
tmp = status & dport->port.read_status_mask;
if (tmp & DZ_PERR) {
flag = TTY_PARITY;
#ifdef DEBUG_DZ
debug_console("PERR\n", 5);
#endif
} else if (tmp & DZ_FERR) {
flag = TTY_FRAME;
#ifdef DEBUG_DZ
debug_console("FERR\n", 5);
#endif
}
if (tmp & DZ_OERR) {
#ifdef DEBUG_DZ
debug_console("OERR\n", 5);
#endif
tty_insert_flip_char(tty, ch, flag);
ch = 0;
flag = TTY_OVERRUN;
}
}
tty_insert_flip_char(tty, ch, flag);
ignore_char:
;
} while (status & DZ_DVAL);
if (tty)
tty_flip_buffer_push(tty);
}
/*
* ------------------------------------------------------------
* transmit_char ()
*
* This routine deals with outputs to any lines.
* ------------------------------------------------------------
*/
static inline void dz_transmit_chars(struct dz_port *dport)
{
struct circ_buf *xmit = &dport->port.info->xmit;
unsigned char tmp;
if (dport->port.x_char) { /* XON/XOFF chars */
dz_out(dport, DZ_TDR, dport->port.x_char);
dport->port.icount.tx++;
dport->port.x_char = 0;
return;
}
/* if nothing to do or stopped or hardware stopped */
if (uart_circ_empty(xmit) || uart_tx_stopped(&dport->port)) {
dz_stop_tx(&dport->port);
return;
}
/*
* if something to do ... (rember the dz has no output fifo so we go
* one char at a time :-<
*/
tmp = xmit->buf[xmit->tail];
xmit->tail = (xmit->tail + 1) & (DZ_XMIT_SIZE - 1);
dz_out(dport, DZ_TDR, tmp);
dport->port.icount.tx++;
if (uart_circ_chars_pending(xmit) < DZ_WAKEUP_CHARS)
uart_write_wakeup(&dport->port);
/* Are we done */
if (uart_circ_empty(xmit))
dz_stop_tx(&dport->port);
}
/*
* ------------------------------------------------------------
* check_modem_status ()
*
* Only valid for the MODEM line duh !
* ------------------------------------------------------------
*/
static inline void check_modem_status(struct dz_port *dport)
{
unsigned short status;
/* if not ne modem line just return */
if (dport->port.line != DZ_MODEM)
return;
status = dz_in(dport, DZ_MSR);
/* it's easy, since DSR2 is the only bit in the register */
if (status)
dport->port.icount.dsr++;
}
/*
* ------------------------------------------------------------
* dz_interrupt ()
*
* this is the main interrupt routine for the DZ chip.
* It deals with the multiple ports.
* ------------------------------------------------------------
*/
static irqreturn_t dz_interrupt(int irq, void *dev)
{
struct dz_port *dport;
unsigned short status;
/* get the reason why we just got an irq */
status = dz_in((struct dz_port *)dev, DZ_CSR);
dport = &dz_ports[LINE(status)];
if (status & DZ_RDONE)
dz_receive_chars(dport);
if (status & DZ_TRDY)
dz_transmit_chars(dport);
/* FIXME: what about check modem status??? --rmk */
return IRQ_HANDLED;
}
/*
* -------------------------------------------------------------------
* Here ends the DZ interrupt routines.
* -------------------------------------------------------------------
*/
static unsigned int dz_get_mctrl(struct uart_port *uport)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned int mctrl = TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
if (dport->port.line == DZ_MODEM) {
/*
* CHECKME: This is a guess from the other code... --rmk
*/
if (dz_in(dport, DZ_MSR) & DZ_MODEM_DSR)
mctrl &= ~TIOCM_DSR;
}
return mctrl;
}
static void dz_set_mctrl(struct uart_port *uport, unsigned int mctrl)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned short tmp;
if (dport->port.line == DZ_MODEM) {
tmp = dz_in(dport, DZ_TCR);
if (mctrl & TIOCM_DTR)
tmp &= ~DZ_MODEM_DTR;
else
tmp |= DZ_MODEM_DTR;
dz_out(dport, DZ_TCR, tmp);
}
}
/*
* -------------------------------------------------------------------
* startup ()
*
* various initialization tasks
* -------------------------------------------------------------------
*/
static int dz_startup(struct uart_port *uport)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned long flags;
unsigned short tmp;
/* The dz lines for the mouse/keyboard must be
* opened using their respective drivers.
*/
if ((dport->port.line == DZ_KEYBOARD) ||
(dport->port.line == DZ_MOUSE))
return -ENODEV;
spin_lock_irqsave(&dport->port.lock, flags);
/* enable the interrupt and the scanning */
tmp = dz_in(dport, DZ_CSR);
tmp |= DZ_RIE | DZ_TIE | DZ_MSE;
dz_out(dport, DZ_CSR, tmp);
spin_unlock_irqrestore(&dport->port.lock, flags);
return 0;
}
/*
* -------------------------------------------------------------------
* shutdown ()
*
* This routine will shutdown a serial port; interrupts are disabled, and
* DTR is dropped if the hangup on close termio flag is on.
* -------------------------------------------------------------------
*/
static void dz_shutdown(struct uart_port *uport)
{
dz_stop_tx(uport);
}
/*
* get_lsr_info - get line status register info
*
* Purpose: Let user call ioctl() to get info when the UART physically
* is emptied. On bus types like RS485, the transmitter must
* release the bus after transmitting. This must be done when
* the transmit shift register is empty, not be done when the
* transmit holding register is empty. This functionality
* allows an RS485 driver to be written in user space.
*/
static unsigned int dz_tx_empty(struct uart_port *uport)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned short status = dz_in(dport, DZ_LPR);
/* FIXME: this appears to be obviously broken --rmk. */
return status ? TIOCSER_TEMT : 0;
}
static void dz_break_ctl(struct uart_port *uport, int break_state)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned long flags;
unsigned short tmp, mask = 1 << uport->line;
spin_lock_irqsave(&uport->lock, flags);
tmp = dz_in(dport, DZ_TCR);
if (break_state)
tmp |= mask;
else
tmp &= ~mask;
dz_out(dport, DZ_TCR, tmp);
spin_unlock_irqrestore(&uport->lock, flags);
}
static void dz_set_termios(struct uart_port *uport, struct termios *termios,
struct termios *old_termios)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned long flags;
unsigned int cflag, baud;
cflag = dport->port.line;
switch (termios->c_cflag & CSIZE) {
case CS5:
cflag |= DZ_CS5;
break;
case CS6:
cflag |= DZ_CS6;
break;
case CS7:
cflag |= DZ_CS7;
break;
case CS8:
default:
cflag |= DZ_CS8;
}
if (termios->c_cflag & CSTOPB)
cflag |= DZ_CSTOPB;
if (termios->c_cflag & PARENB)
cflag |= DZ_PARENB;
if (termios->c_cflag & PARODD)
cflag |= DZ_PARODD;
baud = uart_get_baud_rate(uport, termios, old_termios, 50, 9600);
switch (baud) {
case 50:
cflag |= DZ_B50;
break;
case 75:
cflag |= DZ_B75;
break;
case 110:
cflag |= DZ_B110;
break;
case 134:
cflag |= DZ_B134;
break;
case 150:
cflag |= DZ_B150;
break;
case 300:
cflag |= DZ_B300;
break;
case 600:
cflag |= DZ_B600;
break;
case 1200:
cflag |= DZ_B1200;
break;
case 1800:
cflag |= DZ_B1800;
break;
case 2000:
cflag |= DZ_B2000;
break;
case 2400:
cflag |= DZ_B2400;
break;
case 3600:
cflag |= DZ_B3600;
break;
case 4800:
cflag |= DZ_B4800;
break;
case 7200:
cflag |= DZ_B7200;
break;
case 9600:
default:
cflag |= DZ_B9600;
}
if (termios->c_cflag & CREAD)
cflag |= DZ_RXENAB;
spin_lock_irqsave(&dport->port.lock, flags);
dz_out(dport, DZ_LPR, cflag);
dport->cflag = cflag;
/* setup accept flag */
dport->port.read_status_mask = DZ_OERR;
if (termios->c_iflag & INPCK)
dport->port.read_status_mask |= DZ_FERR | DZ_PERR;
/* characters to ignore */
uport->ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR)
dport->port.ignore_status_mask |= DZ_FERR | DZ_PERR;
spin_unlock_irqrestore(&dport->port.lock, flags);
}
static const char *dz_type(struct uart_port *port)
{
return "DZ";
}
static void dz_release_port(struct uart_port *port)
{
/* nothing to do */
}
static int dz_request_port(struct uart_port *port)
{
return 0;
}
static void dz_config_port(struct uart_port *port, int flags)
{
if (flags & UART_CONFIG_TYPE)
port->type = PORT_DZ;
}
/*
* verify the new serial_struct (for TIOCSSERIAL).
*/
static int dz_verify_port(struct uart_port *port, struct serial_struct *ser)
{
int ret = 0;
if (ser->type != PORT_UNKNOWN && ser->type != PORT_DZ)
ret = -EINVAL;
if (ser->irq != port->irq)
ret = -EINVAL;
return ret;
}
static struct uart_ops dz_ops = {
.tx_empty = dz_tx_empty,
.get_mctrl = dz_get_mctrl,
.set_mctrl = dz_set_mctrl,
.stop_tx = dz_stop_tx,
.start_tx = dz_start_tx,
.stop_rx = dz_stop_rx,
.enable_ms = dz_enable_ms,
.break_ctl = dz_break_ctl,
.startup = dz_startup,
.shutdown = dz_shutdown,
.set_termios = dz_set_termios,
.type = dz_type,
.release_port = dz_release_port,
.request_port = dz_request_port,
.config_port = dz_config_port,
.verify_port = dz_verify_port,
};
static void __init dz_init_ports(void)
{
static int first = 1;
struct dz_port *dport;
unsigned long base;
int i;
if (!first)
return;
first = 0;
if (mips_machtype == MACH_DS23100 ||
mips_machtype == MACH_DS5100)
base = CKSEG1ADDR(KN01_SLOT_BASE + KN01_DZ11);
else
base = CKSEG1ADDR(KN02_SLOT_BASE + KN02_DZ11);
for (i = 0, dport = dz_ports; i < DZ_NB_PORT; i++, dport++) {
spin_lock_init(&dport->port.lock);
dport->port.membase = (char *) base;
dport->port.iotype = UPIO_PORT;
dport->port.irq = dec_interrupt[DEC_IRQ_DZ11];
dport->port.line = i;
dport->port.fifosize = 1;
dport->port.ops = &dz_ops;
dport->port.flags = UPF_BOOT_AUTOCONF;
}
}
static void dz_reset(struct dz_port *dport)
{
dz_out(dport, DZ_CSR, DZ_CLR);
while (dz_in(dport, DZ_CSR) & DZ_CLR);
/* FIXME: cpu_relax? */
iob();
/* enable scanning */
dz_out(dport, DZ_CSR, DZ_MSE);
}
#ifdef CONFIG_SERIAL_DZ_CONSOLE
static void dz_console_putchar(struct uart_port *uport, int ch)
{
struct dz_port *dport = (struct dz_port *)uport;
unsigned long flags;
int loops = 2500;
unsigned short tmp = (unsigned char)ch;
/* this code sends stuff out to serial device - spinning its
wheels and waiting. */
spin_lock_irqsave(&dport->port.lock, flags);
/* spin our wheels */
while (((dz_in(dport, DZ_CSR) & DZ_TRDY) != DZ_TRDY) && loops--)
/* FIXME: cpu_relax, udelay? --rmk */
;
/* Actually transmit the character. */
dz_out(dport, DZ_TDR, tmp);
spin_unlock_irqrestore(&dport->port.lock, flags);
}
/*
* -------------------------------------------------------------------
* dz_console_print ()
*
* dz_console_print is registered for printk.
* The console must be locked when we get here.
* -------------------------------------------------------------------
*/
static void dz_console_print(struct console *cons,
const char *str,
unsigned int count)
{
struct dz_port *dport = &dz_ports[CONSOLE_LINE];
#ifdef DEBUG_DZ
prom_printf((char *) str);
#endif
uart_console_write(&dport->port, str, count, dz_console_putchar);
}
static int __init dz_console_setup(struct console *co, char *options)
{
struct dz_port *dport = &dz_ports[CONSOLE_LINE];
int baud = 9600;
int bits = 8;
int parity = 'n';
int flow = 'n';
int ret;
unsigned short mask, tmp;
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
dz_reset(dport);
ret = uart_set_options(&dport->port, co, baud, parity, bits, flow);
if (ret == 0) {
mask = 1 << dport->port.line;
tmp = dz_in(dport, DZ_TCR); /* read the TX flag */
if (!(tmp & mask)) {
tmp |= mask; /* set the TX flag */
dz_out(dport, DZ_TCR, tmp);
}
}
return ret;
}
static struct console dz_sercons =
{
.name = "ttyS",
.write = dz_console_print,
.device = uart_console_device,
.setup = dz_console_setup,
.flags = CON_CONSDEV | CON_PRINTBUFFER,
.index = CONSOLE_LINE,
};
void __init dz_serial_console_init(void)
{
dz_init_ports();
register_console(&dz_sercons);
}
#define SERIAL_DZ_CONSOLE &dz_sercons
#else
#define SERIAL_DZ_CONSOLE NULL
#endif /* CONFIG_SERIAL_DZ_CONSOLE */
static struct uart_driver dz_reg = {
.owner = THIS_MODULE,
.driver_name = "serial",
.dev_name = "ttyS%d",
.major = TTY_MAJOR,
.minor = 64,
.nr = DZ_NB_PORT,
.cons = SERIAL_DZ_CONSOLE,
};
int __init dz_init(void)
{
unsigned long flags;
int ret, i;
printk("%s%s\n", dz_name, dz_version);
dz_init_ports();
save_flags(flags);
cli();
#ifndef CONFIG_SERIAL_DZ_CONSOLE
/* reset the chip */
dz_reset(&dz_ports[0]);
#endif
/* order matters here... the trick is that flags
is updated... in request_irq - to immediatedly obliterate
it is unwise. */
restore_flags(flags);
if (request_irq(dz_ports[0].port.irq, dz_interrupt,
IRQF_DISABLED, "DZ", &dz_ports[0]))
panic("Unable to register DZ interrupt");
ret = uart_register_driver(&dz_reg);
if (ret != 0)
return ret;
for (i = 0; i < DZ_NB_PORT; i++)
uart_add_one_port(&dz_reg, &dz_ports[i].port);
return ret;
}
MODULE_DESCRIPTION("DECstation DZ serial driver");
MODULE_LICENSE("GPL");