?? serial.c
字號:
/* * linux/kernel/serial.c * * Copyright (C) 1991, 1992 Linus Torvalds * * Extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92. Now * much more extensible to support other serial cards based on the * 16450/16550A UART's. Added support for the AST FourPort and the * Accent Async board. * * set_serial_info fixed to set the flags, custom divisor, and uart * type fields. Fix suggested by Michael K. Johnson 12/12/92. * * This module exports the following rs232 io functions: * * long rs_init(long); * int rs_open(struct tty_struct * tty, struct file * filp) */#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/timer.h>#include <linux/tty.h>#include <linux/serial.h>#include <linux/interrupt.h>#include <linux/config.h>#include <linux/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/ptrace.h>#include <asm/system.h>#include <asm/io.h>#include <asm/segment.h>#include <asm/bitops.h>/* * Serial driver configuration section. Here are the various options: * * CONFIG_AUTO_IRQ * Enables automatic IRQ detection. I've put in some * fixes to this which should make this work much more * cleanly than it used to in 0.98pl2-6. It should be * much less vulnerable to false IRQs now. * * CONFIG_AST_FOURPORT * Enables support for the AST Fourport serial port. * * CONFIG_ACCENT_ASYNC * Enables support for the Accent Async 4 port serial * port. * * CONFIG_HUB6 * Enables support for the venerable Bell Technologies * HUB6 card. */#undef ISR_HACK/* * rs_event - Bitfield of serial lines that events pending * to be processed at the next clock tick. * IRQ_timeout - How long the timeout should be for each IRQ * should be after the IRQ has been active. * IRQ_timer - Array of timeout values for each interrupt IRQ. * This is based on jiffies; not offsets. * * We assume here that int's are 32 bits, so an array of two gives us * 64 lines, which is the maximum we can support. */static int rs_event[2];static struct async_struct *IRQ_ports[16];static int IRQ_active;static unsigned long IRQ_timer[16];static int IRQ_timeout[16];static volatile int rs_irq_triggered;static volatile int rs_triggered;static int rs_wild_int_mask;static void autoconfig(struct async_struct * info);static void change_speed(unsigned int line); /* * This assumes you have a 1.8432 MHz clock for your UART. * * It'd be nice if someone built a serial card with a 24.576 MHz * clock, since the 16550A is capable of handling a top speed of 1.5 * megabits/second; but this requires the faster clock. */#define BASE_BAUD ( 1843200 / 16 )#ifdef CONFIG_AUTO_IRQ#define AUTO_IRQ_FLAG ASYNC_AUTO_IRQ#else#define AUTO_IRQ_FLAG 0#endif/* Standard COM flags (except for COM4, because of the 8514 problem) */#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST | AUTO_IRQ_FLAG)#define STD_COM4_FLAGS (ASYNC_BOOT_AUTOCONF | AUTO_IRQ_FLAG)#ifdef CONFIG_AST_FOURPORT#define FOURPORT_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_FOURPORT | AUTO_IRQ_FLAG)#else#define FOURPORT_FLAGS (ASYNC_FOURPORT | AUTO_IRQ_FLAG)#endif#ifdef CONFIG_ACCENT_ASYNC#define ACCENT_FLAGS (ASYNC_BOOT_AUTOCONF | AUTO_IRQ_FLAG)#else#define ACCENT_FLAGS AUTO_IRQ_FLAG#endif#ifdef CONFIG_BOCA#define BOCA_FLAGS (ASYNC_BOOT_AUTOCONF | AUTO_IRQ_FLAG)#else#define BOCA_FLAGS AUTO_IRQ_FLAG#endif#ifdef CONFIG_HUB6#define HUB6_FLAGS (ASYNC_BOOT_AUTOCONF)#else#define HUB6_FLAGS 0#endif /* * The following define the access methods for the HUB6 card. All * access is through two ports for all 24 possible chips. The card is * selected through the high 2 bits, the port on that card with the * "middle" 3 bits, and the register on that port with the bottom * 3 bits. * * While the access port and interrupt is configurable, the default * port locations are 0x302 for the port control register, and 0x303 * for the data read/write register. Normally, the interrupt is at irq3 * but can be anything from 3 to 7 inclusive. Note tht using 3 will * require disabling com2. */#define C_P(card,port) (((card)<<6|(port)<<3) + 1)struct async_struct rs_table[] = { /* UART CLK PORT IRQ FLAGS */ { BASE_BAUD, 0x3F8, 4, STD_COM_FLAGS }, /* ttyS0 */ { BASE_BAUD, 0x2F8, 3, STD_COM_FLAGS }, /* ttyS1 */ { BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ { BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */ { BASE_BAUD, 0x1A0, 9, FOURPORT_FLAGS }, /* ttyS4 */ { BASE_BAUD, 0x1A8, 9, FOURPORT_FLAGS }, /* ttyS5 */ { BASE_BAUD, 0x1B0, 9, FOURPORT_FLAGS }, /* ttyS6 */ { BASE_BAUD, 0x1B8, 9, FOURPORT_FLAGS }, /* ttyS7 */ { BASE_BAUD, 0x2A0, 5, FOURPORT_FLAGS }, /* ttyS8 */ { BASE_BAUD, 0x2A8, 5, FOURPORT_FLAGS }, /* ttyS9 */ { BASE_BAUD, 0x2B0, 5, FOURPORT_FLAGS }, /* ttyS10 */ { BASE_BAUD, 0x2B8, 5, FOURPORT_FLAGS }, /* ttyS11 */ { BASE_BAUD, 0x330, 4, ACCENT_FLAGS }, /* ttyS12 */ { BASE_BAUD, 0x338, 4, ACCENT_FLAGS }, /* ttyS13 */ { BASE_BAUD, 0x000, 0, 0 }, /* ttyS14 (spare; user configurable) */ { BASE_BAUD, 0x000, 0, 0 }, /* ttyS15 (spare; user configurable) */ { BASE_BAUD, 0x100, 12, BOCA_FLAGS }, /* ttyS16 */ { BASE_BAUD, 0x108, 12, BOCA_FLAGS }, /* ttyS17 */ { BASE_BAUD, 0x110, 12, BOCA_FLAGS }, /* ttyS18 */ { BASE_BAUD, 0x118, 12, BOCA_FLAGS }, /* ttyS19 */ { BASE_BAUD, 0x120, 12, BOCA_FLAGS }, /* ttyS20 */ { BASE_BAUD, 0x128, 12, BOCA_FLAGS }, /* ttyS21 */ { BASE_BAUD, 0x130, 12, BOCA_FLAGS }, /* ttyS22 */ { BASE_BAUD, 0x138, 12, BOCA_FLAGS }, /* ttyS23 */ { BASE_BAUD, 0x140, 12, BOCA_FLAGS }, /* ttyS24 */ { BASE_BAUD, 0x148, 12, BOCA_FLAGS }, /* ttyS25 */ { BASE_BAUD, 0x150, 12, BOCA_FLAGS }, /* ttyS26 */ { BASE_BAUD, 0x158, 12, BOCA_FLAGS }, /* ttyS27 */ { BASE_BAUD, 0x160, 12, BOCA_FLAGS }, /* ttyS28 */ { BASE_BAUD, 0x168, 12, BOCA_FLAGS }, /* ttyS29 */ { BASE_BAUD, 0x170, 12, BOCA_FLAGS }, /* ttyS30 */ { BASE_BAUD, 0x178, 12, BOCA_FLAGS }, /* ttyS31 *//* You can have up to four HUB6's in the system, but I've only * included two cards here for a total of twelve ports. */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,0) }, /* ttyS32 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,1) }, /* ttyS33 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,2) }, /* ttyS34 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,3) }, /* ttyS35 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,4) }, /* ttyS36 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(0,5) }, /* ttyS37 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,0) }, /* ttyS32 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,1) }, /* ttyS33 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,2) }, /* ttyS34 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,3) }, /* ttyS35 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,4) }, /* ttyS36 */ { BASE_BAUD, 0x302, 3, HUB6_FLAGS, C_P(1,5) }, /* ttyS37 */};#define NR_PORTS (sizeof(rs_table)/sizeof(struct async_struct))/* * This is used to figure out the divsor speeds and the timeouts */static int baud_table[] = { 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 0 };static void rs_throttle(struct tty_struct * tty, int status);static inline unsigned int serial_in(struct async_struct *info, int offset){ if (info->hub6) { outb(info->hub6 - 1 + offset, info->port); return inb(info->port+1); } else return inb(info->port + offset);}static inline unsigned int serial_inp(struct async_struct *info, int offset){ if (info->hub6) { outb(info->hub6 - 1 + offset, info->port); return inb_p(info->port+1); } else return inb_p(info->port + offset);}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 outb(value, info->port+offset);}static inline void serial_outp(struct async_struct *info, int offset, int value){ if (info->hub6) { outb(info->hub6 - 1 + offset, info->port); outb_p(value, info->port+1); } else outb_p(value, info->port+offset);}/* * ------------------------------------------------------------ * rs_stop() and rs_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */static void rs_stop(struct tty_struct *tty){ struct async_struct *info; info = rs_table + DEV_TO_SL(tty->line); if (info->flags & ASYNC_CLOSING) { tty->stopped = 0; tty->hw_stopped = 0; return; } info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;#ifdef ISR_HACK serial_out(info, UART_IER, info->IER);#endif}static void rs_start(struct tty_struct *tty){ struct async_struct *info; info = rs_table + DEV_TO_SL(tty->line); info->IER = (UART_IER_MSI | UART_IER_RLSI | UART_IER_THRI | UART_IER_RDI);#ifdef ISR_HACK serial_out(info, UART_IER, info->IER);#endif}/* * ---------------------------------------------------------------------- * * Here starts the interrupt handling routines. All of the following * subroutines are declared as inline and are folded into * rs_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: * * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c * * and look at the resulting assemble code in serial.s. * * - Ted Ts'o (tytso@mit.edu), 7-Mar-93 * ----------------------------------------------------------------------- *//* * This is the serial driver's interrupt routine while we are probing * for submarines. */static void rs_probe(int irq){ rs_irq_triggered = irq; rs_triggered |= 1 << irq; return;}/* * This routine is used by the interrupt handler to schedule * processing in the software interrupt portion of the driver. */static inline void rs_sched_event(struct async_struct *info, int event){ info->event |= 1 << event; set_bit(info->line, rs_event); mark_bh(SERIAL_BH);}static inline void receive_chars(struct async_struct *info, int *status){ struct tty_queue * queue; int head, tail, ch;/* * Just like the LEFT(x) macro, except it uses the loal tail * and head variables. */#define VLEFT ((tail-head-1)&(TTY_BUF_SIZE-1)) queue = &info->tty->read_q; head = queue->head; tail = queue->tail; do { ch = serial_inp(info, UART_RX); /* * There must be at least 2 characters * free in the queue; otherwise we punt. */ if (VLEFT < 2) break; if (*status & info->read_status_mask) { set_bit(head, &info->tty->readq_flags); if (*status & (UART_LSR_BI)) { queue->buf[head++]= TTY_BREAK; rs_sched_event(info, RS_EVENT_BREAK); } else if (*status & UART_LSR_PE) queue->buf[head++]= TTY_PARITY; else if (*status & UART_LSR_FE) queue->buf[head++]= TTY_FRAME; else if (*status & UART_LSR_OE) queue->buf[head++]= TTY_OVERRUN; head &= TTY_BUF_SIZE-1; } queue->buf[head++] = ch; head &= TTY_BUF_SIZE-1; } while ((*status = serial_inp(info, UART_LSR)) & UART_LSR_DR); queue->head = head; if ((VLEFT < RQ_THRESHOLD_LW) && !set_bit(TTY_RQ_THROTTLED, &info->tty->flags)) rs_throttle(info->tty, TTY_THROTTLE_RQ_FULL); rs_sched_event(info, RS_EVENT_READ_PROCESS);#ifdef SERIAL_DEBUG_INTR printk("DR...");#endif}static inline void transmit_chars(struct async_struct *info, int *done_work){ struct tty_queue * queue; int head, tail, count; queue = &info->tty->write_q; head = queue->head; tail = queue->tail; if (head==tail && !info->x_char) { info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;#ifdef ISR_HACK serial_out(info, UART_IER, info->IER);#endif return; } count = info->xmit_fifo_size; if (info->x_char) { serial_outp(info, UART_TX, info->x_char); info->x_char = 0; count--; } while (count-- && (tail != head)) { serial_outp(info, UART_TX, queue->buf[tail++]); tail &= TTY_BUF_SIZE-1; } queue->tail = tail; if (VLEFT > WAKEUP_CHARS) { rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); if (info->tty->write_data_cnt) { set_bit(info->tty->line, &tty_check_write); mark_bh(TTY_BH); } }#ifdef SERIAL_DEBUG_INTR printk("THRE...");#endif (*done_work)++;}static inline int check_modem_status(struct async_struct *info){ int status; status = serial_in(info, UART_MSR); if ((status & UART_MSR_DDCD) && !C_CLOCAL(info->tty)) {#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR)) printk("ttys%d CD now %s...", info->line, (status & UART_MSR_DCD) ? "on" : "off");#endif if (status & UART_MSR_DCD) rs_sched_event(info, RS_EVENT_OPEN_WAKEUP); else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) && (info->flags & ASYNC_CALLOUT_NOHUP))) {#ifdef SERIAL_DEBUG_OPEN printk("scheduling hangup...");#endif rs_sched_event(info, RS_EVENT_HANGUP); } } if (C_CRTSCTS(info->tty) && !(info->flags & ASYNC_CLOSING)) { if (info->tty->hw_stopped) { if (status & UART_MSR_CTS) {#ifdef SERIAL_DEBUG_INTR printk("CTS tx start...");#endif info->tty->hw_stopped = 0; rs_start(info->tty); return 1; } } else { if (!(status & UART_MSR_CTS)) {#ifdef SERIAL_DEBUG_INTR printk("CTS tx stop...");#endif info->tty->hw_stopped = 1; rs_stop(info->tty); } } } return 0;}static inline void figure_RS_timer(void){ int timeout = jiffies + 60*HZ; /* 60 seconds; really big :-) */ int i, mask; if (!IRQ_active) return; for (i=0, mask = 1; mask <= IRQ_active; i++, mask <<= 1) { if (!(mask & IRQ_active)) continue; if (IRQ_timer[i] < timeout) timeout = IRQ_timer[i]; } timer_table[RS_TIMER].expires = timeout; timer_active |= 1 << RS_TIMER;}/* * This is the serial driver's generic interrupt routine */static void rs_interrupt(int irq){ int status; struct async_struct * info; int done, done_work, pass_number, recheck_count; rs_irq_triggered = irq; rs_triggered |= 1 << irq; info = IRQ_ports[irq]; done = 1; done_work = 0; pass_number = 0; while (info) { if (info->tty && info->tty->termios && (!pass_number || !(serial_inp(info, UART_IIR) & UART_IIR_NO_INT))) { done = 0; status = serial_inp(info, UART_LSR); if (status & UART_LSR_DR) { receive_chars(info, &status); done_work++; } recheck_count = 0; recheck_write: if (status & UART_LSR_THRE) { wake_up_interruptible(&info->xmit_wait); if (!info->tty->stopped && !info->tty->hw_stopped) transmit_chars(info, &done_work); } if (check_modem_status(info) && (recheck_count++ <= 64)) goto recheck_write;#ifdef SERIAL_DEBUG_INTR
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -