📄 vacserial.c
字号:
/* $Id: vacserial.c,v 1.3 1999/08/17 22:18:37 ralf Exp $ * vacserial.c: VAC UART serial driver * This code stealed and adopted from linux/drivers/char/serial.c * See that for author info * * Copyright (C) 1998 Gleb Raiko & Vladimir Roganov */#undef SERIAL_PARANOIA_CHECK#define CONFIG_SERIAL_NOPAUSE_IO#define SERIAL_DO_RESTART#define CONFIG_SERIAL_SHARE_IRQ/* Set of debugging defines */#undef SERIAL_DEBUG_INTR#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT#define RS_STROBE_TIME (10*HZ)#define RS_ISR_PASS_LIMIT 2 /* Beget is not a super-computer (old=256) */#define IRQ_T(state) \ ((state->flags & ASYNC_SHARE_IRQ) ? SA_SHIRQ : SA_INTERRUPT)#define SERIAL_INLINE #if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)#define DBG_CNT(s) baget_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#define QUAD_UART_SPEED /* Useful for Baget *//* * End of serial driver configuration section. */#include <linux/config.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/signal.h>#include <linux/sched.h>#include <linux/timer.h>#include <linux/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/serial.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>#ifdef CONFIG_SERIAL_CONSOLE#include <linux/console.h>#endif#include <asm/system.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/uaccess.h>#include <asm/bitops.h>#include <asm/serial.h>#include <asm/baget/baget.h>#define BAGET_VAC_UART_IRQ 0x35/* * Implementation note: * It was descovered by means of advanced electronic tools, * if the driver works via TX_READY interrupts then VIC generates * strange self-eliminating traps. Thus, the driver is rewritten to work * via TX_EMPTY *//* VAC-specific check/debug switches */#undef CHECK_REG_INDEX#undef DEBUG_IO_PORT_A#ifdef SERIAL_INLINE#define _INLINE_ inline#endif static char *serial_name = "VAC Serial driver";static char *serial_version = "4.26";static DECLARE_TASK_QUEUE(tq_serial);static struct tty_driver serial_driver, callout_driver;static int serial_refcount;/* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256 /* * IRQ_timeout - How long the timeout should be for each IRQ * should be after the IRQ has been active. */static struct async_struct *IRQ_ports[NR_IRQS];static int IRQ_timeout[NR_IRQS];#ifdef CONFIG_SERIAL_CONSOLEstatic struct console sercons;#endifstatic void autoconfig(struct serial_state * info);static void change_speed(struct async_struct *info);static void rs_wait_until_sent(struct tty_struct *tty, int timeout);static void rs_timer(unsigned long dummy);static struct timer_list vacs_timer;/* * Here we define the default xmit fifo size used for each type of * UART */static struct serial_uart_config uart_config[] = { { "unknown", 1, 0 }, /* Must go first -- used as unasigned */ { "VAC UART", 1, 0 }}; #define VAC_UART_TYPE 1 /* Just index in above array */static struct serial_state rs_table[] = {/* * VAC has tricky layout for pair of his SIO registers, * so we need special function to access ones. * To identify port we use their TX offset */ { 0, 9600, VAC_UART_B_TX, BAGET_VAC_UART_IRQ, STD_COM_FLAGS }, /* VAC UART B */ { 0, 9600, VAC_UART_A_TX, BAGET_VAC_UART_IRQ, STD_COM_FLAGS } /* VAC UART A */};#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);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;}/* To unify UART A/B access we will use following function to compute register offsets by register index. */#define VAC_UART_MODE 0#define VAC_UART_TX 1#define VAC_UART_RX 2#define VAC_UART_INT_MASK 3#define VAC_UART_INT_STATUS 4#define VAC_UART_REG_NR 5static inline int uart_offset_map(unsigned long port, int reg_index) { static const unsigned int ind_to_reg[VAC_UART_REG_NR][NR_PORTS] = { { VAC_UART_B_MODE, VAC_UART_A_MODE }, { VAC_UART_B_TX, VAC_UART_A_TX }, { VAC_UART_B_RX, VAC_UART_A_RX }, { VAC_UART_B_INT_MASK, VAC_UART_A_INT_MASK }, { VAC_UART_B_INT_STATUS, VAC_UART_A_INT_STATUS } };#ifdef CHECK_REG_INDEX if (reg_index > VAC_UART_REG_NR) panic("vacserial: bad reg_index");#endif return ind_to_reg[reg_index][port == VAC_UART_B_TX ? 0 : 1];}static inline unsigned int serial_inw(struct async_struct *info, int offset){ int val = vac_inw(uart_offset_map(info->port,offset));#ifdef DEBUG_IO_PORT_A if (info->port == VAC_UART_A_TX) printk("UART_A_IN: reg = 0x%04x, val = 0x%04x\n", uart_offset_map(info->port,offset), val);#endif return val;}static inline unsigned int serial_inp(struct async_struct *info, int offset){ return serial_inw(info, offset);}static inline unsigned int serial_in(struct async_struct *info, int offset){ return serial_inw(info, offset);}static inline void serial_outw(struct async_struct *info,int offset, int value){#ifdef DEBUG_IO_PORT_A if (info->port == VAC_UART_A_TX) printk("UART_A_OUT: offset = 0x%04x, val = 0x%04x\n", uart_offset_map(info->port,offset), value);#endif vac_outw(value, uart_offset_map(info->port,offset));}static inline void serial_outp(struct async_struct *info,int offset, int value){ serial_outw(info,offset,value);}static inline void serial_out(struct async_struct *info,int offset, int value){ serial_outw(info,offset,value);}/* * ------------------------------------------------------------ * 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 & VAC_UART_INT_TX_EMPTY) { info->IER &= ~VAC_UART_INT_TX_EMPTY; serial_out(info, VAC_UART_INT_MASK, info->IER); } 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_cnt && info->xmit_buf && !(info->IER & VAC_UART_INT_TX_EMPTY)) { info->IER |= VAC_UART_INT_TX_EMPTY; serial_out(info, VAC_UART_INT_MASK, info->IER); } 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){ struct tty_struct *tty = info->tty; unsigned short rx; unsigned char ch; int ignored = 0; struct async_icount *icount; icount = &info->state->icount; do { rx = serial_inw(info, VAC_UART_RX); ch = VAC_UART_RX_DATA_MASK & rx; if (tty->flip.count >= TTY_FLIPBUF_SIZE) break; *tty->flip.char_buf_ptr = ch; icount->rx++; #ifdef SERIAL_DEBUG_INTR baget_printk("DR%02x:%02x...", rx, *status);#endif *tty->flip.flag_buf_ptr = 0; if (*status & (VAC_UART_STATUS_RX_BREAK_CHANGE | VAC_UART_STATUS_RX_ERR_PARITY | VAC_UART_STATUS_RX_ERR_FRAME | VAC_UART_STATUS_RX_ERR_OVERRUN)) { /* * For statistics only */ if (*status & VAC_UART_STATUS_RX_BREAK_CHANGE) { *status &= ~(VAC_UART_STATUS_RX_ERR_FRAME | VAC_UART_STATUS_RX_ERR_PARITY); icount->brk++; } else if (*status & VAC_UART_STATUS_RX_ERR_PARITY) icount->parity++; else if (*status & VAC_UART_STATUS_RX_ERR_FRAME) icount->frame++; if (*status & VAC_UART_STATUS_RX_ERR_OVERRUN) 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) { if (++ignored > 100) break; goto ignore_char; } *status &= info->read_status_mask; if (*status & (VAC_UART_STATUS_RX_BREAK_CHANGE)) {#ifdef SERIAL_DEBUG_INTR baget_printk("handling break....");#endif *tty->flip.flag_buf_ptr = TTY_BREAK; if (info->flags & ASYNC_SAK) do_SAK(tty); } else if (*status & VAC_UART_STATUS_RX_ERR_PARITY) *tty->flip.flag_buf_ptr = TTY_PARITY; else if (*status & VAC_UART_STATUS_RX_ERR_FRAME) *tty->flip.flag_buf_ptr = TTY_FRAME; if (*status & VAC_UART_STATUS_RX_ERR_OVERRUN) { /* * 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: *status = serial_inw(info, VAC_UART_INT_STATUS); } while ((*status & VAC_UART_STATUS_RX_READY)); tty_flip_buffer_push(tty);}static _INLINE_ void transmit_chars(struct async_struct *info, int *intr_done){ int count; if (info->x_char) { serial_outw(info, VAC_UART_TX, (((unsigned short)info->x_char)<<8)); info->state->icount.tx++; info->x_char = 0; if (intr_done) *intr_done = 0; return; } if ((info->xmit_cnt <= 0) || info->tty->stopped || info->tty->hw_stopped) { info->IER &= ~VAC_UART_INT_TX_EMPTY; serial_outw(info, VAC_UART_INT_MASK, info->IER); return; } count = info->xmit_fifo_size; do { serial_out(info, VAC_UART_TX, (unsigned short)info->xmit_buf[info->xmit_tail++] \ << 8); info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1); info->state->icount.tx++; if (--info->xmit_cnt <= 0) break; } while (--count > 0); if (info->xmit_cnt < WAKEUP_CHARS) rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);#ifdef SERIAL_DEBUG_INTR baget_printk("THRE...");#endif if (intr_done) *intr_done = 0; if (info->xmit_cnt <= 0) { info->IER &= ~VAC_UART_INT_TX_EMPTY; serial_outw(info, VAC_UART_INT_MASK, info->IER); }}static _INLINE_ void check_modem_status(struct async_struct *info){#if 0 /* VAC hasn't modem control */ wake_up_interruptible(&info->open_wait); rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); #endif}#ifdef CONFIG_SERIAL_SHARE_IRQ/* * Specific functions needed for VAC UART interrupt enter/leave */#define VAC_INT_CTRL_UART_ENABLE \ (VAC_INT_CTRL_TIMER_PIO10|VAC_INT_CTRL_UART_B_PIO7|VAC_INT_CTRL_UART_A_PIO7)#define VAC_INT_CTRL_UART_DISABLE(info) \ (VAC_INT_CTRL_TIMER_PIO10 | \ ((info->port == VAC_UART_A_TX) ? \ (VAC_INT_CTRL_UART_A_DISABLE|VAC_INT_CTRL_UART_B_PIO7) : \ (VAC_INT_CTRL_UART_A_PIO7|VAC_INT_CTRL_UART_B_DISABLE)))/*
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -