📄 core.c
字号:
/* * linux/drivers/char/serial_core.c * * Driver core for serial ports * * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. * * Copyright 1999 ARM Limited * Copyright (C) 2000-2001 Deep Blue Solutions Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: core.c,v 1.20.2.5 2002/03/13 15:22:26 rmk Exp $ * */#include <linux/config.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/ptrace.h>#include <linux/ioport.h>#include <linux/mm.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/circ_buf.h>#include <linux/console.h>#include <linux/sysrq.h>#include <linux/pm.h>#include <linux/serial_core.h>#include <asm/system.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/uaccess.h>#include <asm/bitops.h>#undef DEBUG#ifndef CONFIG_PM#define pm_access(pm) do { } while (0)#define pm_unregister(pm) do { } while (0)#endif/* * tmp_buf is used as a temporary buffer by serial_write. We need to * lock it in case the copy_from_user blocks while swapping in a page, * and some other program tries to do a serial write at the same time. * Since the lock will only come under contention when the system is * swapping and available memory is low, it makes sense to share one * buffer across all the serial ports, since it significantly saves * memory if large numbers of serial ports are open. */static u_char *tmp_buf;static DECLARE_MUTEX(tmp_buf_sem);/* * This is used to lock changes in serial line configuration. */static DECLARE_MUTEX(port_sem);#define HIGH_BITS_OFFSET ((sizeof(long)-sizeof(int))*8)static void uart_change_speed(struct uart_info *info, struct termios *old_termios);static void uart_wait_until_sent(struct tty_struct *tty, int timeout);/* * This routine is used by the interrupt handler to schedule processing in * the software interrupt portion of the driver. It is expected that * interrupts will be disabled (and so the tasklet will be prevented * from running (CHECK)). */void uart_event(struct uart_info *info, int event){ info->event |= 1 << event; tasklet_schedule(&info->tlet);}static void uart_stop(struct tty_struct *tty){ struct uart_info *info = tty->driver_data; unsigned long flags; spin_lock_irqsave(&info->lock, flags); info->ops->stop_tx(info->port, 1); spin_unlock_irqrestore(&info->lock, flags);}static void __uart_start(struct tty_struct *tty){ struct uart_info *info = tty->driver_data; if (info->xmit.head != info->xmit.tail && info->xmit.buf && !tty->stopped && !tty->hw_stopped) info->ops->start_tx(info->port, 1, 1);}static void uart_start(struct tty_struct *tty){ struct uart_info *info = tty->driver_data; unsigned long flags; pm_access(info->state->pm); spin_lock_irqsave(&info->lock, flags); __uart_start(tty); spin_unlock_irqrestore(&info->lock, flags);}static void uart_tasklet_action(unsigned long data){ struct uart_info *info = (struct uart_info *)data; struct tty_struct *tty; tty = info->tty; if (!tty || !test_and_clear_bit(EVT_WRITE_WAKEUP, &info->event)) return; if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait);}static inline void uart_update_altspeed(struct uart_info *info){ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) info->tty->alt_speed = 57600; if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) info->tty->alt_speed = 115200; if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) info->tty->alt_speed = 230400; if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) info->tty->alt_speed = 460800;}static int uart_startup(struct uart_info *info){ unsigned long flags; unsigned long page; int retval = 0; page = get_zeroed_page(GFP_KERNEL); if (!page) return -ENOMEM; save_flags(flags); cli(); if (info->flags & ASYNC_INITIALIZED) { free_page(page); goto errout; } if (info->port->type == PORT_UNKNOWN) { if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); free_page(page); goto errout; } if (info->xmit.buf) free_page(page); else info->xmit.buf = (unsigned char *) page; info->mctrl = 0; retval = info->ops->startup(info->port, info); if (retval) { if (capable(CAP_SYS_ADMIN)) { if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); retval = 0; } goto errout; } if (info->tty) clear_bit(TTY_IO_ERROR, &info->tty->flags); info->xmit.head = info->xmit.tail = 0; /* * Set up the tty->alt_speed kludge */ if (info->tty) uart_update_altspeed(info); /* * and set the speed of the serial port */ uart_change_speed(info, NULL); /* * Setup the RTS and DTR signals once the port * is open and ready to respond. */ if (info->tty->termios->c_cflag & CBAUD) info->mctrl |= TIOCM_RTS | TIOCM_DTR; info->ops->set_mctrl(info->port, info->mctrl); info->flags |= ASYNC_INITIALIZED; retval = 0;errout: restore_flags(flags); return retval;}/* * 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 uart_shutdown(struct uart_info *info){ unsigned long flags; if (!(info->flags & ASYNC_INITIALIZED)) return; save_flags(flags); cli(); /* Disable interrupts */ /* * clear delta_msr_wait queue to avoid mem leaks: we may free the irq * here so the queue might never be woken up */ wake_up_interruptible(&info->delta_msr_wait); /* * Free the IRQ and disable the port */ info->ops->shutdown(info->port, info); if (info->xmit.buf) { unsigned long pg = (unsigned long) info->xmit.buf; info->xmit.buf = NULL; free_page(pg); } if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) info->mctrl &= ~(TIOCM_DTR|TIOCM_RTS); info->ops->set_mctrl(info->port, info->mctrl); /* kill off our tasklet */ tasklet_kill(&info->tlet); if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); info->flags &= ~ASYNC_INITIALIZED; restore_flags(flags);}static inline u_int uart_calculate_quot(struct uart_info *info, u_int baud){ u_int quot; /* Special case: B0 rate */ if (!baud) baud = 9600; /* Old HI/VHI/custom speed handling */ if (baud == 38400 && ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) quot = info->state->custom_divisor; else quot = info->port->uartclk / (16 * baud); return quot;}static void uart_change_speed(struct uart_info *info, struct termios *old_termios){ struct uart_port *port = info->port; u_int quot, baud, cflag, bits, try; /* * If we have no tty, termios, or the port does not exist, * then we can't set the parameters for this port. */ if (!info->tty || !info->tty->termios || info->port->type == PORT_UNKNOWN) return; cflag = info->tty->termios->c_cflag; /* byte size and parity */ switch (cflag & CSIZE) { case CS5: bits = 7; break; case CS6: bits = 8; break; case CS7: bits = 9; break; default: bits = 10; break; // CS8 } if (cflag & CSTOPB) bits++; if (cflag & PARENB) bits++; for (try = 0; try < 2; try ++) { /* Determine divisor based on baud rate */ baud = tty_get_baud_rate(info->tty); quot = uart_calculate_quot(info, baud); if (quot) break; /* * Oops, the quotient was zero. Try again with * the old baud rate if possible. */ info->tty->termios->c_cflag &= ~CBAUD; if (old_termios) { info->tty->termios->c_cflag |= (old_termios->c_cflag & CBAUD); old_termios = NULL; continue; } /* * As a last resort, if the quotient is zero, * default to 9600 bps */ info->tty->termios->c_cflag |= B9600; } info->timeout = (port->fifosize * HZ * bits * quot) / (port->uartclk / 16); info->timeout += HZ/50; /* Add .02 seconds of slop */ if (cflag & CRTSCTS) info->flags |= ASYNC_CTS_FLOW; else info->flags &= ~ASYNC_CTS_FLOW; if (cflag & CLOCAL) info->flags &= ~ASYNC_CHECK_CD; else info->flags |= ASYNC_CHECK_CD; /* * Set up parity check flag */#define RELEVENT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) pm_access(info->state->pm); info->ops->change_speed(port, cflag, info->tty->termios->c_iflag, quot);}static void uart_put_char(struct tty_struct *tty, u_char ch){ struct uart_info *info = tty->driver_data; unsigned long flags; if (!tty || !info->xmit.buf) return; spin_lock_irqsave(&info->lock, flags); if (CIRC_SPACE(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE) != 0) { info->xmit.buf[info->xmit.head] = ch; info->xmit.head = (info->xmit.head + 1) & (UART_XMIT_SIZE - 1); } spin_unlock_irqrestore(&info->lock, flags);}static void uart_flush_chars(struct tty_struct *tty){ uart_start(tty);}static int uart_write(struct tty_struct *tty, int from_user, const u_char * buf, int count){ struct uart_info *info = tty->driver_data; unsigned long flags; int c, ret = 0; if (!tty || !info->xmit.buf || !tmp_buf) return 0; if (from_user) { down(&tmp_buf_sem); while (1) { int c1; c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE); if (count < c) c = count; if (c <= 0) break; c -= copy_from_user(tmp_buf, buf, c); if (!c) { if (!ret) ret = -EFAULT; break; } spin_lock_irqsave(&info->lock, flags); c1 = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE); if (c1 < c) c = c1; memcpy(info->xmit.buf + info->xmit.head, tmp_buf, c); info->xmit.head = (info->xmit.head + c) & (UART_XMIT_SIZE - 1); spin_unlock_irqrestore(&info->lock, flags); buf += c; count -= c; ret += c; } up(&tmp_buf_sem); } else { spin_lock_irqsave(&info->lock, flags); while (1) { c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE); if (count < c) c = count; if (c <= 0) break; memcpy(info->xmit.buf + info->xmit.head, buf, c); info->xmit.head = (info->xmit.head + c) & (UART_XMIT_SIZE - 1); buf += c; count -= c; ret += c; } spin_unlock_irqrestore(&info->lock, flags); } uart_start(tty); return ret;}static int uart_write_room(struct tty_struct *tty){ struct uart_info *info = tty->driver_data; return CIRC_SPACE(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE);}static int uart_chars_in_buffer(struct tty_struct *tty){ struct uart_info *info = tty->driver_data; return CIRC_CNT(info->xmit.head, info->xmit.tail, UART_XMIT_SIZE);}static void uart_flush_buffer(struct tty_struct *tty){ struct uart_info *info = tty->driver_data; unsigned long flags;#ifdef DEBUG printk("uart_flush_buffer(%d) called\n", MINOR(tty->device) - tty->driver.minor_start);#endif spin_lock_irqsave(&info->lock, flags); info->xmit.head = info->xmit.tail = 0; spin_unlock_irqrestore(&info->lock, flags); wake_up_interruptible(&tty->write_wait); if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty);}/* * This function is used to send a high-priority XON/XOFF character to * the device */static void uart_send_xchar(struct tty_struct *tty, char ch){ struct uart_info *info = tty->driver_data; info->port->x_char = ch; if (ch) info->ops->start_tx(info->port, 1, 0);}static void uart_throttle(struct tty_struct *tty){ struct uart_info *info = tty->driver_data; unsigned long flags; if (I_IXOFF(tty)) uart_send_xchar(tty, STOP_CHAR(tty)); if (tty->termios->c_cflag & CRTSCTS) { spin_lock_irqsave(&info->lock, flags); info->mctrl &= ~TIOCM_RTS; info->ops->set_mctrl(info->port, info->mctrl); spin_unlock_irqrestore(&info->lock, flags); }}static void uart_unthrottle(struct tty_struct *tty){ struct uart_info *info = (struct uart_info *) tty->driver_data; unsigned long flags; if (I_IXOFF(tty)) { if (info->port->x_char) info->port->x_char = 0; else uart_send_xchar(tty, START_CHAR(tty)); } if (tty->termios->c_cflag & CRTSCTS) { spin_lock_irqsave(&info->lock, flags); info->mctrl |= TIOCM_RTS; info->ops->set_mctrl(info->port, info->mctrl); spin_unlock_irqrestore(&info->lock, flags); }}static int uart_get_info(struct uart_info *info, struct serial_struct *retinfo){ struct uart_state *state = info->state; struct uart_port *port = info->port; struct serial_struct tmp; memset(&tmp, 0, sizeof(tmp)); tmp.type = port->type; tmp.line = port->line; tmp.port = port->iobase; if (HIGH_BITS_OFFSET) tmp.port_high = port->iobase >> HIGH_BITS_OFFSET; tmp.irq = port->irq; tmp.flags = port->flags; tmp.xmit_fifo_size = port->fifosize; tmp.baud_base = port->uartclk / 16; tmp.close_delay = state->close_delay; tmp.closing_wait = state->closing_wait; tmp.custom_divisor = state->custom_divisor; tmp.hub6 = port->hub6; tmp.io_type = port->iotype; tmp.iomem_reg_shift= port->regshift; tmp.iomem_base = (void *)port->mapbase; if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) return -EFAULT; return 0;}static int uart_set_info(struct uart_info *info, struct serial_struct *newinfo)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -