diff options
Diffstat (limited to 'drivers/char/serial.c')
-rw-r--r-- | drivers/char/serial.c | 633 |
1 files changed, 510 insertions, 123 deletions
diff --git a/drivers/char/serial.c b/drivers/char/serial.c index bd983057c..c662eb678 100644 --- a/drivers/char/serial.c +++ b/drivers/char/serial.c @@ -11,6 +11,8 @@ * set_serial_info fixed to set the flags, custom divisor, and uart * type fields. Fix suggested by Michael K. Johnson 12/12/92. * + * Mips JAZZ support by Andreas Busse, andy@waldorf-gmbh.de + * * This module exports the following rs232 io functions: * * long rs_init(long); @@ -32,11 +34,16 @@ #include <linux/fcntl.h> #include <linux/ptrace.h> #include <linux/major.h> +#include <linux/ioport.h> +#include <linux/mm.h> #include <asm/system.h> #include <asm/io.h> #include <asm/segment.h> #include <asm/bitops.h> +#ifdef CONFIG_MIPS_JAZZ +# include <asm/jazz.h> +#endif DECLARE_TASK_QUEUE(tq_serial); @@ -65,11 +72,13 @@ static int serial_refcount; #define SERIAL_PARANOIA_CHECK #define CONFIG_SERIAL_NOPAUSE_IO #define SERIAL_DO_RESTART -#define CONFIG_SERIAL_NEW_ISR #undef SERIAL_DEBUG_INTR -#undef SERIAL_DEBUG_OPEN -#undef SERIAL_DEBUG_FLOW +#define SERIAL_DEBUG_OPEN +#define SERIAL_DEBUG_FLOW + +#define RS_STROBE_TIME 10 +#define RS_ISR_PASS_LIMIT 256 #define _INLINE_ inline @@ -79,6 +88,7 @@ static int serial_refcount; */ static struct async_struct *IRQ_ports[16]; +static struct rs_multiport_struct rs_multiport[16]; static int IRQ_timeout[16]; static volatile int rs_irq_triggered; static volatile int rs_triggered; @@ -95,6 +105,7 @@ static void change_speed(struct async_struct *info); * megabits/second; but this requires the faster clock. */ #define BASE_BAUD ( 1843200 / 16 ) +#define JAZZ_BASE_BAUD ( 8000000 / 16 ) /* ( 3072000 / 16) */ /* Standard COM flags (except for COM4, because of the 8514 problem) */ #define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST ) @@ -123,8 +134,13 @@ static void change_speed(struct async_struct *info); struct async_struct rs_table[] = { /* UART CLK PORT IRQ FLAGS */ +#ifdef CONFIG_MIPS_JAZZ + { 0, JAZZ_BASE_BAUD, JAZZ_SERIAL1_BASE, 3, STD_COM_FLAGS }, /* ttyS0 */ + { 0, JAZZ_BASE_BAUD, JAZZ_SERIAL2_BASE, 4, STD_COM_FLAGS }, /* ttyS1 */ +#else { 0, BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */ { 0, BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */ +#endif { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */ @@ -235,7 +251,12 @@ static inline unsigned int serial_in(struct async_struct *info, int offset) return inb(info->port+1); } else #endif - return inb(info->port + offset); +#ifdef CONFIG_MIPS_JAZZ + if (info->port >= JAZZ_LOCAL_IO_SPACE) + return (*((volatile unsigned char *)info->port + offset)); + else +#endif + return inb(info->port + offset); } static inline unsigned int serial_inp(struct async_struct *info, int offset) @@ -246,10 +267,15 @@ static inline unsigned int serial_inp(struct async_struct *info, int offset) return inb_p(info->port+1); } else #endif +#ifdef CONFIG_MIPS_JAZZ + if (info->port >= JAZZ_LOCAL_IO_SPACE) + return (*((volatile unsigned char *)info->port + offset)); + else +#endif #ifdef CONFIG_SERIAL_NOPAUSE_IO - return inb(info->port + offset); + return inb(info->port + offset); #else - return inb_p(info->port + offset); + return inb_p(info->port + offset); #endif } @@ -259,9 +285,14 @@ static inline void serial_out(struct async_struct *info, int offset, int value) if (info->hub6) { outb(info->hub6 - 1 + offset, info->port); outb(value, info->port+1); - } else + } else #endif - outb(value, info->port+offset); +#ifdef CONFIG_MIPS_JAZZ + if (info->port >= JAZZ_LOCAL_IO_SPACE) + *((volatile unsigned char *)info->port + offset) = value; + else +#endif + outb(value, info->port+offset); } static inline void serial_outp(struct async_struct *info, int offset, @@ -273,10 +304,15 @@ static inline void serial_outp(struct async_struct *info, int offset, outb_p(value, info->port+1); } else #endif +#ifdef CONFIG_MIPS_JAZZ + if (info->port >= JAZZ_LOCAL_IO_SPACE) + *((volatile unsigned char *)info->port + offset) = value; + else +#endif #ifdef CONFIG_SERIAL_NOPAUSE_IO - outb(value, info->port+offset); + outb(value, info->port+offset); #else - outb_p(value, info->port+offset); + outb_p(value, info->port+offset); #endif } @@ -345,7 +381,7 @@ static void rs_start(struct tty_struct *tty) * This is the serial driver's interrupt routine while we are probing * for submarines. */ -static void rs_probe(int irq) +static void rs_probe(int irq, struct pt_regs * regs) { rs_irq_triggered = irq; rs_triggered |= 1 << irq; @@ -369,11 +405,15 @@ static _INLINE_ void receive_chars(struct async_struct *info, { struct tty_struct *tty = info->tty; unsigned char ch; + int ignored = 0; do { ch = serial_inp(info, UART_RX); - if (*status & info->ignore_status_mask) + if (*status & info->ignore_status_mask) { + if (++ignored > 100) + break; goto ignore_char; + } if (tty->flip.count >= TTY_FLIPBUF_SIZE) break; tty->flip.count++; @@ -414,9 +454,7 @@ static _INLINE_ void transmit_chars(struct async_struct *info, int *intr_done) if ((info->xmit_cnt <= 0) || info->tty->stopped || info->tty->hw_stopped) { info->IER &= ~UART_IER_THRI; -#ifdef CONFIG_SERIAL_NEW_ISR serial_out(info, UART_IER, info->IER); -#endif return; } @@ -439,9 +477,7 @@ static _INLINE_ void transmit_chars(struct async_struct *info, int *intr_done) if (info->xmit_cnt <= 0) { info->IER &= ~UART_IER_THRI; -#ifdef CONFIG_SERIAL_NEW_ISR serial_out(info, UART_IER, info->IER); -#endif } } @@ -463,7 +499,8 @@ static _INLINE_ void check_modem_status(struct async_struct *info) #ifdef SERIAL_DEBUG_OPEN printk("scheduling hangup..."); #endif - rs_sched_event(info, RS_EVENT_HANGUP); + queue_task_irq_off(&info->tqueue_hangup, + &tq_scheduler); } } if (info->flags & ASYNC_CTS_FLOW) { @@ -474,9 +511,7 @@ static _INLINE_ void check_modem_status(struct async_struct *info) #endif info->tty->hw_stopped = 0; info->IER |= UART_IER_THRI; -#ifdef CONFIG_SERIAL_NEW_ISR serial_out(info, UART_IER, info->IER); -#endif rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); return; } @@ -487,24 +522,23 @@ static _INLINE_ void check_modem_status(struct async_struct *info) #endif info->tty->hw_stopped = 1; info->IER &= ~UART_IER_THRI; -#ifdef CONFIG_SERIAL_NEW_ISR serial_out(info, UART_IER, info->IER); -#endif } } } } -#ifdef CONFIG_SERIAL_NEW_ISR /* * This is the serial driver's generic interrupt routine */ -static void rs_interrupt(int irq) +static void rs_interrupt(int irq, struct pt_regs * regs) { int status; struct async_struct * info; int pass_counter = 0; struct async_struct *end_mark = 0; + int first_multi = 0; + struct rs_multiport_struct *multi; #ifdef SERIAL_DEBUG_INTR printk("rs_interrupt(%d)...", irq); @@ -514,6 +548,10 @@ static void rs_interrupt(int irq) if (!info) return; + multi = &rs_multiport[irq]; + if (multi->port_monitor) + first_multi = inb(multi->port_monitor); + do { if (!info->tty || (serial_in(info, UART_IIR) & UART_IIR_NO_INT)) { @@ -539,7 +577,7 @@ static void rs_interrupt(int irq) info = info->next_port; if (!info) { info = IRQ_ports[irq]; - if (pass_counter++ > 64) { + if (pass_counter++ > RS_ISR_PASS_LIMIT) { #if 0 printk("rs loop break\n"); #endif @@ -548,6 +586,9 @@ static void rs_interrupt(int irq) continue; } } while (end_mark != info); + if (multi->port_monitor) + printk("rs port monitor (normal) irq %d: 0x%x, 0x%x\n", + info->irq, first_multi, inb(multi->port_monitor)); #ifdef SERIAL_DEBUG_INTR printk("end.\n"); #endif @@ -556,11 +597,13 @@ static void rs_interrupt(int irq) /* * This is the serial driver's interrupt routine for a single port */ -static void rs_interrupt_single(int irq) +static void rs_interrupt_single(int irq, struct pt_regs * regs) { int status; int pass_counter = 0; + int first_multi = 0; struct async_struct * info; + struct rs_multiport_struct *multi; #ifdef SERIAL_DEBUG_INTR printk("rs_interrupt_single(%d)...", irq); @@ -570,6 +613,10 @@ static void rs_interrupt_single(int irq) if (!info || !info->tty) return; + multi = &rs_multiport[irq]; + if (multi->port_monitor) + first_multi = inb(multi->port_monitor); + do { status = serial_inp(info, UART_LSR) & info->read_status_mask; #ifdef SERIAL_DEBUG_INTR @@ -580,7 +627,7 @@ static void rs_interrupt_single(int irq) check_modem_status(info); if (status & UART_LSR_THRE) transmit_chars(info, 0); - if (pass_counter++ > 64) { + if (pass_counter++ > RS_ISR_PASS_LIMIT) { #if 0 printk("rs_single loop break.\n"); #endif @@ -588,102 +635,95 @@ static void rs_interrupt_single(int irq) } } while (!(serial_in(info, UART_IIR) & UART_IIR_NO_INT)); info->last_active = jiffies; + if (multi->port_monitor) + printk("rs port monitor (single) irq %d: 0x%x, 0x%x\n", + info->irq, first_multi, inb(multi->port_monitor)); #ifdef SERIAL_DEBUG_INTR printk("end.\n"); #endif } -#else /* CONFIG_SERIAL_NEW_ISR */ - /* - * This is the serial driver's generic interrupt routine + * This is the serial driver's for multiport boards */ -static void rs_interrupt(int irq) +static void rs_interrupt_multi(int irq, struct pt_regs * regs) { int status; struct async_struct * info; - int done = 1, pass_counter = 0; + int pass_counter = 0; + int first_multi= 0; + struct rs_multiport_struct *multi; - #ifdef SERIAL_DEBUG_INTR - printk("rs_interrupt(%d)...", irq); + printk("rs_interrupt_multi(%d)...", irq); #endif info = IRQ_ports[irq]; if (!info) return; + multi = &rs_multiport[irq]; + if (!multi->port1) { + /* Should never happen */ + printk("rs_interrupt_multi: NULL port1!\n"); + return; + } + if (multi->port_monitor) + first_multi = inb(multi->port_monitor); while (1) { - if (!info->tty) + if (!info->tty || + (serial_in(info, UART_IIR) & UART_IIR_NO_INT)) goto next; - serial_outp(info, UART_IER, 0); + info->last_active = jiffies; + status = serial_inp(info, UART_LSR) & info->read_status_mask; - if (status & UART_LSR_DR) { +#ifdef SERIAL_DEBUG_INTR + printk("status = %x...", status); +#endif + if (status & UART_LSR_DR) receive_chars(info, &status); - done = 0; - } check_modem_status(info); if (status & UART_LSR_THRE) - transmit_chars(info, &done); + transmit_chars(info, 0); next: - info = info->next_port; - if (!info) { - info = IRQ_ports[irq]; - if (done) - break; - done = 1; - if (pass_counter++ > 64) { -#if 0 - printk("rs loop break\n"); + info = info->next_port; + if (info) + continue; + + info = IRQ_ports[irq]; + if (pass_counter++ > RS_ISR_PASS_LIMIT) { +#if 1 + printk("rs_multi loop break\n"); #endif - break; /* Prevent infinite loops */ - } + break; /* Prevent infinite loops */ } - } - - /* - * Reset the IER registers; info is already set up from the - * above while loop. - */ - do - serial_outp(info, UART_IER, info->IER); - while ((info = info->next_port) != NULL); -} - -/* - * This is the serial driver's interrupt routine for a single port - */ -static void rs_interrupt_single(int irq) -{ - int status; - struct async_struct * info; - - + if (multi->port_monitor) + printk("rs port monitor irq %d: 0x%x, 0x%x\n", + info->irq, first_multi, + inb(multi->port_monitor)); + if ((inb(multi->port1) & multi->mask1) != multi->match1) + continue; + if (!multi->port2) + break; + if ((inb(multi->port2) & multi->mask2) != multi->match2) + continue; + if (!multi->port3) + break; + if ((inb(multi->port3) & multi->mask3) != multi->match3) + continue; + if (!multi->port4) + break; + if ((inb(multi->port4) & multi->mask4) == multi->match4) + continue; + break; + } #ifdef SERIAL_DEBUG_INTR - printk("rs_interrupt_single(%d)...", irq); + printk("end.\n"); #endif - - info = IRQ_ports[irq]; - if (!info || !info->tty) - return; - - serial_outp(info, UART_IER, 0); - status = serial_inp(info, UART_LSR) & info->read_status_mask; - if (status & UART_LSR_DR) - receive_chars(info, &status); - check_modem_status(info); - if (status & UART_LSR_THRE) - transmit_chars(info, 0); - - /* - * Reset the IER register - */ - serial_outp(info, UART_IER, info->IER); } -#endif /* CONFIG_SERIAL_NEW_ISR */ /* * ------------------------------------------------------------------- @@ -714,12 +754,6 @@ static void do_softint(void *private_) if (!tty) return; - if (clear_bit(RS_EVENT_HANGUP, &info->event)) { - tty_hangup(tty); - wake_up_interruptible(&info->open_wait); - info->flags &= ~(ASYNC_NORMAL_ACTIVE| - ASYNC_CALLOUT_ACTIVE); - } if (clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) @@ -729,6 +763,28 @@ static void do_softint(void *private_) } /* + * This routine is called from the scheduler tqueue when the interrupt + * routine has signalled that a hangup has occurred. The path of + * hangup processing is: + * + * serial interrupt routine -> (scheduler tqueue) -> + * do_serial_hangup() -> tty->hangup() -> rs_hangup() + * + */ +static void do_serial_hangup(void *private_) +{ + struct async_struct *info = (struct async_struct *) private_; + struct tty_struct *tty; + + tty = info->tty; + if (!tty) + return; + + tty_hangup(tty); +} + + +/* * This subroutine is called when the RS_TIMER goes off. It is used * by the serial driver to handle ports that do not have an interrupt * (irq=0). This doesn't work very well for 16450's, but gives barely @@ -741,7 +797,7 @@ static void rs_timer(void) struct async_struct *info; unsigned int i; - if ((jiffies - last_strobe) >= 60*HZ) { + if ((jiffies - last_strobe) >= RS_STROBE_TIME*HZ) { for (i=1; i < 16; i++) { info = IRQ_ports[i]; if (!info) @@ -754,19 +810,22 @@ static void rs_timer(void) serial_out(info, UART_IER, info->IER); info = info->next_port; } while (info); - rs_interrupt(i); + if (rs_multiport[i].port1) + rs_interrupt_multi(i, NULL); + else + rs_interrupt(i, NULL); } else - rs_interrupt_single(i); + rs_interrupt_single(i, NULL); sti(); } } last_strobe = jiffies; - timer_table[RS_TIMER].expires = jiffies + 60 * HZ; + timer_table[RS_TIMER].expires = jiffies + RS_STROBE_TIME * HZ; timer_active |= 1 << RS_TIMER; if (IRQ_ports[0]) { cli(); - rs_interrupt(0); + rs_interrupt(0, NULL); sti(); timer_table[RS_TIMER].expires = jiffies + IRQ_timeout[0] - 2; @@ -822,11 +881,11 @@ static void free_all_interrupts(int irq_lines) static void figure_IRQ_timeout(int irq) { struct async_struct *info; - int timeout = 6000; /* 60 seconds === a long time :-) */ + int timeout = 60*HZ; /* 60 seconds === a long time :-) */ info = IRQ_ports[irq]; if (!info) { - IRQ_timeout[irq] = 6000; + IRQ_timeout[irq] = 60*HZ; return; } while (info) { @@ -844,7 +903,7 @@ static int startup(struct async_struct * info) unsigned short ICP; unsigned long flags; int retval; - void (*handler)(int); + void (*handler)(int, struct pt_regs *); if (info->flags & ASYNC_INITIALIZED) return 0; @@ -871,7 +930,11 @@ static int startup(struct async_struct * info) * Clear the FIFO buffers and disable them * (they will be reenabled in change_speed()) */ - if (info->type == PORT_16550A) { + if (info->type == PORT_16650) { + serial_outp(info, UART_FCR, (UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT)); + info->xmit_fifo_size = 1; /* disabled for now */ + } else if (info->type == PORT_16550A) { serial_outp(info, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); info->xmit_fifo_size = 16; @@ -900,7 +963,10 @@ static int startup(struct async_struct * info) !IRQ_ports[info->irq]->next_port)) { if (IRQ_ports[info->irq]) { free_irq(info->irq); - handler = rs_interrupt; + if (rs_multiport[info->irq].port1) + handler = rs_interrupt_multi; + else + handler = rs_interrupt; } else handler = rs_interrupt_single; @@ -936,6 +1002,10 @@ static int startup(struct async_struct * info) info->MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2; info->MCR_noint = UART_MCR_DTR | UART_MCR_RTS; } +#if defined(__alpha__) && !defined(CONFIG_PCI) + info->MCR |= UART_MCR_OUT1 | UART_MCR_OUT2; + info->MCR_noint |= UART_MCR_OUT1 | UART_MCR_OUT2; +#endif if (info->irq == 0) info->MCR = info->MCR_noint; serial_outp(info, UART_MCR, info->MCR); @@ -1127,8 +1197,16 @@ static void change_speed(struct async_struct *info) return; } /* byte size and parity */ - cval = cflag & (CSIZE | CSTOPB); - cval >>= 4; + switch (cflag & CSIZE) { + case CS5: cval = 0x00; break; + case CS6: cval = 0x01; break; + case CS7: cval = 0x02; break; + case CS8: cval = 0x03; break; + default: cval = 0x00; break; /* too keep GCC shut... */ + } + if (cflag & CSTOPB) { + cval |= 0x04; + } if (cflag & PARENB) cval |= UART_LCR_PARITY; if (!(cflag & PARODD)) @@ -1138,6 +1216,18 @@ static void change_speed(struct async_struct *info) fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; else fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_8; + } else if (info->type == PORT_16650) { + /* + * On the 16650, we disable the FIFOs altogether + * because of a design bug in how the implement + * things. We could support it by completely changing + * how we handle the interrupt driver, but not today.... + * + * N.B. Because there's no way to set a FIFO trigger + * at 1 char, we'd probably disable at speed below + * 2400 baud anyway... + */ + fcr = 0; } else fcr = 0; @@ -1182,7 +1272,8 @@ static void change_speed(struct async_struct *info) info->read_status_mask |= UART_LSR_OE; } } - + +printk("change_speed: quot = %08x\n",quot); cli(); serial_outp(info, UART_LCR, cval | UART_LCR_DLAB); /* set DLAB */ serial_outp(info, UART_DLL, quot & 0xff); /* LS of divisor */ @@ -1394,6 +1485,7 @@ static int get_serial_info(struct async_struct * info, tmp.flags = info->flags; tmp.baud_base = info->baud_base; tmp.close_delay = info->close_delay; + tmp.closing_wait = info->closing_wait; tmp.custom_divisor = info->custom_divisor; tmp.hub6 = info->hub6; memcpy_tofs(retinfo,&tmp,sizeof(*retinfo)); @@ -1461,7 +1553,9 @@ static int set_serial_info(struct async_struct * info, info->custom_divisor = new_serial.custom_divisor; info->type = new_serial.type; info->close_delay = new_serial.close_delay; + info->closing_wait = new_serial.closing_wait; + release_region(info->port,8); if (change_port || change_irq) { /* * We need to shutdown the serial port at the old @@ -1472,6 +1566,9 @@ static int set_serial_info(struct async_struct * info, info->port = new_serial.port; info->hub6 = new_serial.hub6; } + if(info->type != PORT_UNKNOWN) + request_region(info->port,8,"serial(set)"); + check_and_exit: if (!info->port || !info->type) @@ -1533,8 +1630,13 @@ static int get_modem_info(struct async_struct * info, unsigned int *value) static int set_modem_info(struct async_struct * info, unsigned int cmd, unsigned int *value) { - unsigned int arg = get_fs_long((unsigned long *) value); + int error; + unsigned int arg; + error = verify_area(VERIFY_READ, value, sizeof(int)); + if (error) + return error; + arg = get_fs_long((unsigned long *) value); switch (cmd) { case TIOCMBIS: if (arg & TIOCM_RTS) { @@ -1657,6 +1759,101 @@ static int check_wild_interrupts(int doprint) return wild_interrupts; } +static int get_multiport_struct(struct async_struct * info, + struct serial_multiport_struct *retinfo) +{ + struct serial_multiport_struct ret; + struct rs_multiport_struct *multi; + + multi = &rs_multiport[info->irq]; + + ret.port_monitor = multi->port_monitor; + + ret.port1 = multi->port1; + ret.mask1 = multi->mask1; + ret.match1 = multi->match1; + + ret.port2 = multi->port2; + ret.mask2 = multi->mask2; + ret.match2 = multi->match2; + + ret.port3 = multi->port3; + ret.mask3 = multi->mask3; + ret.match3 = multi->match3; + + ret.port4 = multi->port4; + ret.mask4 = multi->mask4; + ret.match4 = multi->match4; + + ret.irq = info->irq; + + memcpy_tofs(retinfo,&ret,sizeof(*retinfo)); + return 0; + +} + +static int set_multiport_struct(struct async_struct * info, + struct serial_multiport_struct *in_multi) +{ + struct serial_multiport_struct new_multi; + struct rs_multiport_struct *multi; + int was_multi, now_multi; + int retval; + void (*handler)(int, struct pt_regs *); + + if (!suser()) + return -EPERM; + if (!in_multi) + return -EFAULT; + memcpy_fromfs(&new_multi, in_multi, + sizeof(struct serial_multiport_struct)); + + if (new_multi.irq != info->irq || info->irq == 0 || + !IRQ_ports[info->irq]) + return -EINVAL; + + multi = &rs_multiport[info->irq]; + was_multi = (multi->port1 != 0); + + multi->port_monitor = new_multi.port_monitor; + + multi->port1 = new_multi.port1; + multi->mask1 = new_multi.mask1; + multi->match1 = new_multi.match1; + + multi->port2 = new_multi.port2; + multi->mask2 = new_multi.mask2; + multi->match2 = new_multi.match2; + + multi->port3 = new_multi.port3; + multi->mask3 = new_multi.mask3; + multi->match3 = new_multi.match3; + + multi->port4 = new_multi.port4; + multi->mask4 = new_multi.mask4; + multi->match4 = new_multi.match4; + + now_multi = (multi->port1 != 0); + + if (IRQ_ports[info->irq]->next_port && + (was_multi != now_multi)) { + free_irq(info->irq); + if (now_multi) + handler = rs_interrupt_multi; + else + handler = rs_interrupt; + + retval = request_irq(info->irq, handler, SA_INTERRUPT, + "serial"); + if (retval) { + printk("Couldn't reallocate serial interrupt " + "driver!!\n"); + } + } + + return 0; +} + static int rs_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg) { @@ -1666,13 +1863,20 @@ static int rs_ioctl(struct tty_struct *tty, struct file * file, if (serial_paranoia_check(info, tty->device, "rs_ioctl")) return -ENODEV; + + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) && + (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + } switch (cmd) { case TCSBRK: /* SVID version: non-zero arg --> no break */ retval = tty_check_change(tty); if (retval) return retval; - wait_until_sent(tty, 0); + tty_wait_until_sent(tty, 0); if (!arg) send_break(info, HZ/4); /* 1/4 second */ return 0; @@ -1680,7 +1884,7 @@ static int rs_ioctl(struct tty_struct *tty, struct file * file, retval = tty_check_change(tty); if (retval) return retval; - wait_until_sent(tty, 0); + tty_wait_until_sent(tty, 0); send_break(info, arg ? arg*(HZ/10) : HZ/4); return 0; case TIOCGSOFTCAR: @@ -1752,6 +1956,16 @@ static int rs_ioctl(struct tty_struct *tty, struct file * file, info, sizeof(struct async_struct)); return 0; + case TIOCSERGETMULTI: + error = verify_area(VERIFY_WRITE, (void *) arg, + sizeof(struct serial_multiport_struct)); + if (error) + return error; + return get_multiport_struct(info, + (struct serial_multiport_struct *) arg); + case TIOCSERSETMULTI: + return set_multiport_struct(info, + (struct serial_multiport_struct *) arg); default: return -ENOIOCTLCMD; } @@ -1773,9 +1987,17 @@ static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios) rs_start(tty); } +#if 0 + /* + * No need to wake up processes in open wait, since they + * sample the CLOCAL flag once, and don't recheck it. + * XXX It's not clear whether the current behavior is correct + * or not. Hence, this may change..... + */ if (!(old_termios->c_cflag & CLOCAL) && (tty->termios->c_cflag & CLOCAL)) wake_up_interruptible(&info->open_wait); +#endif } /* @@ -1838,16 +2060,22 @@ static void rs_close(struct tty_struct *tty, struct file * filp) if (info->flags & ASYNC_CALLOUT_ACTIVE) info->callout_termios = *tty->termios; /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + tty->closing = 1; + if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, info->closing_wait); + /* * At this point we stop accepting input. To do this, we * disable the receive line status interrupts, and tell the * interrupt driver to stop checking the data ready bit in the * line status register. */ info->IER &= ~UART_IER_RLSI; - serial_out(info, UART_IER, info->IER); info->read_status_mask &= ~UART_LSR_DR; if (info->flags & ASYNC_INITIALIZED) { - wait_until_sent(tty, 3000); /* 30 seconds timeout */ + serial_out(info, UART_IER, info->IER); /* * Before we drop DTR, make sure the UART transmitter * has completely drained; this is especially @@ -1867,6 +2095,7 @@ static void rs_close(struct tty_struct *tty, struct file * filp) tty->driver.flush_buffer(tty); if (tty->ldisc.flush_buffer) tty->ldisc.flush_buffer(tty); + tty->closing = 0; info->event = 0; info->tty = 0; if (tty->ldisc.num != ldiscs[N_TTY].num) { @@ -1901,6 +2130,7 @@ void rs_hangup(struct tty_struct *tty) if (serial_paranoia_check(info, tty->device, "rs_hangup")) return; + rs_flush_buffer(tty); shutdown(info); info->event = 0; info->count = 0; @@ -1957,19 +2187,25 @@ static int block_til_ready(struct tty_struct *tty, struct file * filp, } /* - * If non-blocking mode is set, then make the check up front - * and then exit. + * If non-blocking mode is set, or the port is not enabled, + * then make the check up front and then exit. */ - if (filp->f_flags & O_NONBLOCK) { + if ((filp->f_flags & O_NONBLOCK) || + (tty->flags & (1 << TTY_IO_ERROR))) { if (info->flags & ASYNC_CALLOUT_ACTIVE) return -EBUSY; info->flags |= ASYNC_NORMAL_ACTIVE; return 0; } - if (info->normal_termios.c_cflag & CLOCAL) - do_clocal = 1; - + if (info->flags & ASYNC_CALLOUT_ACTIVE) { + if (info->normal_termios.c_cflag & CLOCAL) + do_clocal = 1; + } else { + if (tty->termios->c_cflag & CLOCAL) + do_clocal = 1; + } + /* * Block waiting for the carrier detect and the line to become * free (i.e., not in use by the callout). While we are in @@ -2096,6 +2332,7 @@ int rs_open(struct tty_struct *tty, struct file * filp) #ifdef SERIAL_DEBUG_OPEN printk("rs_open ttys%d successful...", info->line); +printk("rs_open ttys%d baud_base = %08x\n",info->line,info->baud_base); #endif return 0; } @@ -2115,7 +2352,7 @@ int rs_open(struct tty_struct *tty, struct file * filp) */ static void show_serial_version(void) { - printk("Serial driver version 4.00 with"); + printk("Serial driver version 4.11 with"); #ifdef CONFIG_HUB6 printk(" HUB-6"); #define SERIAL_OPT @@ -2289,6 +2526,10 @@ static void autoconfig(struct async_struct * info) if (info->flags & ASYNC_AUTO_IRQ) info->irq = do_auto_irq(info); + scratch2 = serial_in(info, UART_LCR); + serial_outp(info, UART_LCR, scratch2 | UART_LCR_DLAB); + serial_outp(info, UART_EFR, 0); /* EFR is the same as FCR */ + serial_outp(info, UART_LCR, scratch2); serial_outp(info, UART_FCR, UART_FCR_ENABLE_FIFO); scratch = serial_in(info, UART_IIR) >> 6; info->xmit_fifo_size = 1; @@ -2303,8 +2544,15 @@ static void autoconfig(struct async_struct * info) info->type = PORT_16550; break; case 3: - info->type = PORT_16550A; - info->xmit_fifo_size = 16; + serial_outp(info, UART_LCR, scratch2 | UART_LCR_DLAB); + if (serial_in(info, UART_EFR) == 0) { + info->type = PORT_16650; + info->xmit_fifo_size = 32; + } else { + info->type = PORT_16550A; + info->xmit_fifo_size = 16; + } + serial_outp(info, UART_LCR, scratch2); break; } if (info->type == PORT_16450) { @@ -2318,11 +2566,20 @@ static void autoconfig(struct async_struct * info) if ((status1 != 0xa5) || (status2 != 0x5a)) info->type = PORT_8250; } + request_region(info->port,8,"serial(auto)"); /* * Reset the UART. */ +#if defined(__alpha__) && !defined(CONFIG_PCI) + /* + * I wonder what DEC did to the OUT1 and OUT2 lines? + * clearing them results in endless interrupts. + */ + serial_outp(info, UART_MCR, 0x0c); +#else serial_outp(info, UART_MCR, 0x00); +#endif serial_outp(info, UART_FCR, (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT)); (void)serial_in(info, UART_RX); @@ -2339,15 +2596,18 @@ long rs_init(long kmem_start) struct async_struct * info; bh_base[SERIAL_BH].routine = do_serial_bh; + enable_bh(SERIAL_BH); timer_table[RS_TIMER].fn = rs_timer; timer_table[RS_TIMER].expires = 0; #ifdef CONFIG_AUTO_IRQ rs_wild_int_mask = check_wild_interrupts(1); #endif +/* printk("in rs_init()\n"); */ for (i = 0; i < 16; i++) { IRQ_ports[i] = 0; IRQ_timeout[i] = 0; + memset(&rs_multiport[i], 0, sizeof(struct rs_multiport_struct)); } show_serial_version(); @@ -2396,11 +2656,13 @@ long rs_init(long kmem_start) callout_driver.major = TTYAUX_MAJOR; callout_driver.subtype = SERIAL_TYPE_CALLOUT; +/* printk("rs_init: register drivers\n"); */ if (tty_register_driver(&serial_driver)) panic("Couldn't register serial driver\n"); if (tty_register_driver(&callout_driver)) panic("Couldn't register callout driver\n"); - + +/* printk("rs_init: setting defaults:\n"); */ for (i = 0, info = rs_table; i < NR_PORTS; i++,info++) { info->magic = SERIAL_MAGIC; info->line = i; @@ -2408,12 +2670,15 @@ long rs_init(long kmem_start) info->type = PORT_UNKNOWN; info->custom_divisor = 0; info->close_delay = 50; + info->closing_wait = 3000; info->x_char = 0; info->event = 0; info->count = 0; info->blocked_open = 0; info->tqueue.routine = do_softint; info->tqueue.data = info; + info->tqueue_hangup.routine = do_serial_hangup; + info->tqueue_hangup.data = info; info->callout_termios =callout_driver.init_termios; info->normal_termios = serial_driver.init_termios; info->open_wait = 0; @@ -2424,6 +2689,7 @@ long rs_init(long kmem_start) info->irq = 9; if (!(info->flags & ASYNC_BOOT_AUTOCONF)) continue; +/* printk("rs_init: autoconfig(%08x)\n",info); */ autoconfig(info); if (info->type == PORT_UNKNOWN) continue; @@ -2443,6 +2709,9 @@ long rs_init(long kmem_start) case PORT_16550A: printk(" is a 16550A\n"); break; + case PORT_16650: + printk(" is a 16650\n"); + break; default: printk("\n"); break; @@ -2523,3 +2792,121 @@ void unregister_serial(int line) printk("tty%02d unloaded\n", info->line); restore_flags(flags); } + +#ifdef CONFIG_REMOTE_DEBUG +#undef PRINT_DEBUG_PORT_INFO + +/* + * This is the interface to the remote debugger stub. + * I've put that here to be able to control the serial + * device more directly. + */ + +static int initialized = 0; + +static int rs_debug_init(struct async_struct *info) +{ + int quot; + + autoconfig(info); /* autoconfigure ttyS0, whatever that is */ + +#ifdef PRINT_DEBUG_PORT_INFO + printk("kgdb debug interface:: tty%02d at 0x%04x", info->line, info->port); + switch (info->type) { + case PORT_8250: + printk(" is a 8250\n"); + break; + case PORT_16450: + printk(" is a 16450\n"); + break; + case PORT_16550: + printk(" is a 16550\n"); + break; + case PORT_16550A: + printk(" is a 16550A\n"); + break; + case PORT_16650: + printk(" is a 16650\n"); + break; + default: + printk(" is of unknown type -- unusable\n"); + break; + } +#endif + + if (info->port == PORT_UNKNOWN) + return -1; + + /* + * Clear all interrupts + */ + + (void)serial_inp(info, UART_LSR); + (void)serial_inp(info, UART_RX); + (void)serial_inp(info, UART_IIR); + (void)serial_inp(info, UART_MSR); + + /* + * Now, initialize the UART + */ + serial_outp(info, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */ + if (info->flags & ASYNC_FOURPORT) { + info->MCR = UART_MCR_DTR | UART_MCR_RTS; + info->MCR_noint = UART_MCR_DTR | UART_MCR_OUT1; + } else { + info->MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2; + info->MCR_noint = UART_MCR_DTR | UART_MCR_RTS; + } + + info->MCR = info->MCR_noint; /* no interrupts, please */ + serial_outp(info, UART_MCR, info->MCR); + + /* + * and set the speed of the serial port + * (currently hardwired to 9600 8N1 + */ + + quot = info->baud_base / 9600; /* baud rate is fixed to 9600 */ + serial_outp(info, UART_LCR, UART_LCR_WLEN8 | UART_LCR_DLAB); /* set DLAB */ + serial_outp(info, UART_DLL, quot & 0xff); /* LS of divisor */ + serial_outp(info, UART_DLM, quot >> 8); /* MS of divisor */ + serial_outp(info, UART_LCR, UART_LCR_WLEN8); /* reset DLAB */ + + return 0; +} + +int putDebugChar(char c) +{ + struct async_struct *info = rs_table; + + if (!initialized) { /* need to init device first */ + if (rs_debug_init(info) == 0) + initialized = 1; + else + return 0; + } + + while ((serial_inp(info, UART_LSR) & UART_LSR_THRE) == 0) + ; + serial_out(info, UART_TX, c); + + return 1; +} + +char getDebugChar(void) +{ + struct async_struct *info = rs_table; + + if (!initialized) { /* need to init device first */ + if (rs_debug_init(info) == 0) + initialized = 1; + else + return 0; + } + while (!(serial_inp(info, UART_LSR) & 1)) + ; + + return(serial_inp(info, UART_RX)); +} + +#endif /* CONFIG_REMOTE_DEBUG */ |