📄 amiserial.c
字号:
/* * linux/drivers/char/amiserial.c * * Serial driver for the amiga builtin port. * * This code was created by taking serial.c version 4.30 from kernel * release 2.3.22, replacing all hardware related stuff with the * corresponding amiga hardware actions, and removing all irrelevant * code. As a consequence, it uses many of the constants and names * associated with the registers and bits of 16550 compatible UARTS - * but only to keep track of status, etc in the state variables. It * was done this was to make it easier to keep the code in line with * (non hardware specific) changes to serial.c. * * The port is registered with the tty driver as minor device 64, and * therefore other ports should should only use 65 upwards. * * Richard Lucock 28/12/99 * * Copyright (C) 1991, 1992 Linus Torvalds * Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, * 1998, 1999 Theodore Ts'o * *//* * Serial driver configuration section. Here are the various options: * * SERIAL_PARANOIA_CHECK * Check the magic number for the async_structure where * ever possible. */#include <linux/config.h>#include <linux/version.h>#undef SERIAL_PARANOIA_CHECK#define SERIAL_DO_RESTART/* Set of debugging defines */#undef SERIAL_DEBUG_INTR#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT/* Sanity checks */#define SERIAL_INLINE #if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)#define DBG_CNT(s) printk("(%s): [%x] refc=%d, serc=%d, ttyc=%d -> %s\n", \ kdevname(tty->device), (info->flags), serial_refcount,info->count,tty->count,s)#else#define DBG_CNT(s)#endif/* * End of serial driver configuration section. */#ifdef MODVERSIONS#include <linux/modversions.h>#endif#include <linux/module.h>#include <linux/types.h>#include <linux/serial.h>#include <linux/serialP.h>#include <linux/serial_reg.h>static char *serial_version = "4.30";#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/kernel.h>#include <linux/timer.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/console.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/malloc.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/setup.h>#include <asm/system.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/bitops.h>#include <asm/amigahw.h>#include <asm/amigaints.h>#ifdef SERIAL_INLINE#define _INLINE_ inline#endifstatic char *serial_name = "Amiga-builtin serial driver";static DECLARE_TASK_QUEUE(tq_serial);static struct tty_driver serial_driver, callout_driver;static int serial_refcount;/* serial subtype definitions */#ifndef SERIAL_TYPE_NORMAL#define SERIAL_TYPE_NORMAL 1#define SERIAL_TYPE_CALLOUT 2#endif/* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256static struct async_struct *IRQ_ports;static unsigned char current_ctl_bits;static void change_speed(struct async_struct *info, struct termios *old);static void rs_wait_until_sent(struct tty_struct *tty, int timeout);static struct serial_state rs_table[1];#define NR_PORTS (sizeof(rs_table)/sizeof(struct serial_state))static struct tty_struct *serial_table[NR_PORTS];static struct termios *serial_termios[NR_PORTS];static struct termios *serial_termios_locked[NR_PORTS];#ifndef MIN#define MIN(a,b) ((a) < (b) ? (a) : (b))#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 unsigned char *tmp_buf;static DECLARE_MUTEX(tmp_buf_sem);#include <asm/uaccess.h>#define serial_isroot() (capable(CAP_SYS_ADMIN))static inline int serial_paranoia_check(struct async_struct *info, kdev_t device, const char *routine){#ifdef SERIAL_PARANOIA_CHECK static const char *badmagic = "Warning: bad magic number for serial struct (%s) in %s\n"; static const char *badinfo = "Warning: null async_struct for (%s) in %s\n"; if (!info) { printk(badinfo, kdevname(device), routine); return 1; } if (info->magic != SERIAL_MAGIC) { printk(badmagic, kdevname(device), routine); return 1; }#endif return 0;}/* some serial hardware definitions */#define SDR_OVRUN (1<<15)#define SDR_RBF (1<<14)#define SDR_TBE (1<<13)#define SDR_TSRE (1<<12)#define SERPER_PARENB (1<<15)#define AC_SETCLR (1<<15)#define AC_UARTBRK (1<<11)#define SER_DTR (1<<7)#define SER_RTS (1<<6)#define SER_DCD (1<<5)#define SER_CTS (1<<4)#define SER_DSR (1<<3)static __inline__ void rtsdtr_ctrl(int bits){ ciab.pra = ((bits & (SER_RTS | SER_DTR)) ^ (SER_RTS | SER_DTR)) | (ciab.pra & ~(SER_RTS | SER_DTR));}/* * ------------------------------------------------------------ * 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 = (struct async_struct *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; save_flags(flags); cli(); if (info->IER & UART_IER_THRI) { info->IER &= ~UART_IER_THRI; /* disable Tx interrupt and remove any pending interrupts */ custom.intena = IF_TBE; mb(); custom.intreq = IF_TBE; mb(); } restore_flags(flags);}static void rs_start(struct tty_struct *tty){ struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_start")) return; save_flags(flags); cli(); if (info->xmit.head != info->xmit.tail && info->xmit.buf && !(info->IER & UART_IER_THRI)) { info->IER |= UART_IER_THRI; custom.intena = IF_SETCLR | IF_TBE; mb(); /* set a pending Tx Interrupt, transmitter should restart now */ custom.intreq = IF_SETCLR | IF_TBE; mb(); } restore_flags(flags);}/* * ---------------------------------------------------------------------- * * 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 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; queue_task(&info->tqueue, &tq_serial); mark_bh(SERIAL_BH);}static _INLINE_ void receive_chars(struct async_struct *info){ int status; int serdatr; struct tty_struct *tty = info->tty; unsigned char ch; struct async_icount *icount; icount = &info->state->icount; status = UART_LSR_DR; /* We obviously have a character! */ serdatr = custom.serdatr; mb(); custom.intreq = IF_RBF; mb(); if((serdatr & 0x1ff) == 0) status |= UART_LSR_BI; if(serdatr & SDR_OVRUN) status |= UART_LSR_OE; ch = serdatr & 0xff; if (tty->flip.count >= TTY_FLIPBUF_SIZE) goto ignore_char; *tty->flip.char_buf_ptr = ch; icount->rx++;#ifdef SERIAL_DEBUG_INTR printk("DR%02x:%02x...", ch, status);#endif *tty->flip.flag_buf_ptr = 0; /* * We don't handle parity or frame errors - but I have left * the code in, since I'm not sure that the errors can't be * detected. */ if (status & (UART_LSR_BI | UART_LSR_PE | UART_LSR_FE | UART_LSR_OE)) { /* * For statistics only */ if (status & UART_LSR_BI) { status &= ~(UART_LSR_FE | UART_LSR_PE); icount->brk++; } else if (status & UART_LSR_PE) icount->parity++; else if (status & UART_LSR_FE) icount->frame++; if (status & UART_LSR_OE) icount->overrun++; /* * Now check to see if character should be * ignored, and mask off conditions which * should be ignored. */ if (status & info->ignore_status_mask) goto ignore_char; status &= info->read_status_mask; if (status & (UART_LSR_BI)) {#ifdef SERIAL_DEBUG_INTR printk("handling break....");#endif *tty->flip.flag_buf_ptr = TTY_BREAK; if (info->flags & ASYNC_SAK) do_SAK(tty); } else if (status & UART_LSR_PE) *tty->flip.flag_buf_ptr = TTY_PARITY; else if (status & UART_LSR_FE) *tty->flip.flag_buf_ptr = TTY_FRAME; if (status & UART_LSR_OE) { /* * Overrun is special, since it's * reported immediately, and doesn't * affect the current character */ if (tty->flip.count < TTY_FLIPBUF_SIZE) { tty->flip.count++; tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; *tty->flip.flag_buf_ptr = TTY_OVERRUN; } } } tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; ignore_char: tty_flip_buffer_push(tty);}static _INLINE_ void transmit_chars(struct async_struct *info){ custom.intreq = IF_TBE; mb(); if (info->x_char) { custom.serdat = info->x_char | 0x100; mb(); info->state->icount.tx++; info->x_char = 0; return; } if (info->xmit.head == info->xmit.tail || info->tty->stopped || info->tty->hw_stopped) { info->IER &= ~UART_IER_THRI; custom.intena = IF_TBE; mb(); return; } custom.serdat = info->xmit.buf[info->xmit.tail++] | 0x100; mb(); info->xmit.tail = info->xmit.tail & (SERIAL_XMIT_SIZE-1); info->state->icount.tx++; if (CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE) < WAKEUP_CHARS) rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);#ifdef SERIAL_DEBUG_INTR printk("THRE...");#endif if (info->xmit.head == info->xmit.tail) { custom.intena = IF_TBE; mb(); info->IER &= ~UART_IER_THRI; }}static _INLINE_ void check_modem_status(struct async_struct *info){ unsigned char status = ciab.pra & (SER_DCD | SER_CTS | SER_DSR); unsigned char dstatus; struct async_icount *icount; /* Determine bits that have changed */ dstatus = status ^ current_ctl_bits; current_ctl_bits = status; if (dstatus) { icount = &info->state->icount; /* update input line counters */ if (dstatus & SER_DSR) icount->dsr++; if (dstatus & SER_DCD) { icount->dcd++;#ifdef CONFIG_HARD_PPS if ((info->flags & ASYNC_HARDPPS_CD) && !(status & SER_DCD)) hardpps();#endif } if (dstatus & SER_CTS) icount->cts++; wake_up_interruptible(&info->delta_msr_wait); } if ((info->flags & ASYNC_CHECK_CD) && (dstatus & SER_DCD)) {#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR)) printk("ttyS%02d CD now %s...", info->line, (!(status & SER_DCD)) ? "on" : "off");#endif if (!(status & SER_DCD)) wake_up_interruptible(&info->open_wait); else if (!((info->flags & ASYNC_CALLOUT_ACTIVE) && (info->flags & ASYNC_CALLOUT_NOHUP))) {#ifdef SERIAL_DEBUG_OPEN printk("doing serial hangup...");#endif if (info->tty) tty_hangup(info->tty); } } if (info->flags & ASYNC_CTS_FLOW) { if (info->tty->hw_stopped) { if (!(status & SER_CTS)) {#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) printk("CTS tx start...");#endif info->tty->hw_stopped = 0; info->IER |= UART_IER_THRI; custom.intena = IF_SETCLR | IF_TBE; mb(); /* set a pending Tx Interrupt, transmitter should restart now */ custom.intreq = IF_SETCLR | IF_TBE; mb(); rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); return; } } else { if ((status & SER_CTS)) {#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW)) printk("CTS tx stop...");#endif info->tty->hw_stopped = 1; info->IER &= ~UART_IER_THRI; /* disable Tx interrupt and remove any pending interrupts */ custom.intena = IF_TBE; mb(); custom.intreq = IF_TBE; mb(); } } }}static void ser_vbl_int( int irq, void *data, struct pt_regs *regs){ /* vbl is just a periodic interrupt we tie into to update modem status */ struct async_struct * info = IRQ_ports; /* * TBD - is it better to unregister from this interrupt or to * ignore it if MSI is clear ? */ if(info->IER & UART_IER_MSI) check_modem_status(info);}static void ser_rx_int(int irq, void *dev_id, struct pt_regs * regs){ struct async_struct * info;#ifdef SERIAL_DEBUG_INTR printk("ser_rx_int...");#endif info = IRQ_ports; if (!info || !info->tty) return; receive_chars(info); info->last_active = jiffies;#ifdef SERIAL_DEBUG_INTR printk("end.\n");#endif}static void ser_tx_int(int irq, void *dev_id, struct pt_regs * regs){ struct async_struct * info; if (custom.serdatr & SDR_TBE) {#ifdef SERIAL_DEBUG_INTR printk("ser_tx_int...");#endif info = IRQ_ports; if (!info || !info->tty) return; transmit_chars(info); info->last_active = jiffies;#ifdef SERIAL_DEBUG_INTR printk("end.\n");#endif }}/* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- *//* * This routine is used to handle the "bottom half" processing for the * serial driver, known also the "software interrupt" processing. * This processing is done at the kernel interrupt level, after the * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This * is where time-consuming activities which can not be done in the * interrupt driver proper are done; the interrupt driver schedules * them using rs_sched_event(), and they get done here. */static void do_serial_bh(void){ run_task_queue(&tq_serial);}static void do_softint(void *private_){ struct async_struct *info = (struct async_struct *) private_; struct tty_struct *tty; tty = info->tty; if (!tty) return; if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) { if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); }}/* * --------------------------------------------------------------- * Low level utility subroutines for the serial driver: routines to
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -