?? 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
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -