📄 sgiserial.c
字号:
/* sgiserial.c: Serial port driver for SGI machines. * * Copyright (C) 1996 David S. Miller (dm@engr.sgi.com) *//* * Note: This driver seems to have been derived from some * version of the sbus/char/zs.c driver. A lot of clean-up * and bug fixes seem to have happened to the Sun driver in * the intervening time. As of 21.09.1999, I have merged in * ONLY the changes necessary to fix observed functional * problems on the Indy. Someone really ought to do a * thorough pass to merge in the rest of the updates. * Better still, someone really ought to make it a common * code module for both platforms. kevink@mips.com * * 20010616 - Klaus Naumann <spock@mgnet.de> : Make serial console work with * any speed - not only 9600 */#include <linux/config.h> /* for CONFIG_REMOTE_DEBUG */#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/major.h>#include <linux/string.h>#include <linux/fcntl.h>#include <linux/mm.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/console.h>#include <linux/init.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/sgialib.h>#include <asm/system.h>#include <asm/bitops.h>#include <asm/sgi/sgihpc.h>#include <asm/sgi/sgint23.h>#include <asm/uaccess.h>#include "sgiserial.h"#define NUM_SERIAL 1 /* One chip on board. */#define NUM_CHANNELS (NUM_SERIAL * 2)struct sgi_zslayout *zs_chips[NUM_SERIAL] = { 0, };struct sgi_zschannel *zs_channels[NUM_CHANNELS] = { 0, 0, };struct sgi_zschannel *zs_conschan;struct sgi_zschannel *zs_kgdbchan;struct sgi_serial zs_soft[NUM_CHANNELS];struct sgi_serial *zs_chain; /* IRQ servicing chain */static int zilog_irq = SGI_SERIAL_IRQ;/* Console hooks... */static int zs_cons_chanout;static int zs_cons_chanin;struct sgi_serial *zs_consinfo;static unsigned char kgdb_regs[16] = { 0, 0, 0, /* write 0, 1, 2 */ (Rx8 | RxENABLE), /* write 3 */ (X16CLK | SB1 | PAR_EVEN), /* write 4 */ (Tx8 | TxENAB), /* write 5 */ 0, 0, 0, /* write 6, 7, 8 */ (NV), /* write 9 */ (NRZ), /* write 10 */ (TCBR | RCBR), /* write 11 */ 0, 0, /* BRG time constant, write 12 + 13 */ (BRENABL), /* write 14 */ (DCDIE) /* write 15 */};static unsigned char zscons_regs[16] = { 0, /* write 0 */ (EXT_INT_ENAB | INT_ALL_Rx), /* write 1 */ 0, /* write 2 */ (Rx8 | RxENABLE), /* write 3 */ (X16CLK), /* write 4 */ (DTR | Tx8 | TxENAB), /* write 5 */ 0, 0, 0, /* write 6, 7, 8 */ (NV | MIE), /* write 9 */ (NRZ), /* write 10 */ (TCBR | RCBR), /* write 11 */ 0, 0, /* BRG time constant, write 12 + 13 */ (BRENABL), /* write 14 */ (DCDIE | CTSIE | TxUIE | BRKIE) /* write 15 */};#define ZS_CLOCK 3672000 /* Zilog input clock rate */DECLARE_TASK_QUEUE(tq_serial);struct tty_driver serial_driver, callout_driver;struct console *sgisercon;static int serial_refcount;/* serial subtype definitions */#define SERIAL_TYPE_NORMAL 1#define SERIAL_TYPE_CALLOUT 2/* number of characters left in xmit buffer before we ask for more */#define WAKEUP_CHARS 256/* Debugging... DEBUG_INTR is bad to use when one of the zs * lines is your console ;( */#undef SERIAL_DEBUG_INTR#undef SERIAL_DEBUG_OPEN#undef SERIAL_DEBUG_FLOW#define RS_STROBE_TIME 10#define RS_ISR_PASS_LIMIT 256#define _INLINE_ inlinestatic void change_speed(struct sgi_serial *info);static struct tty_struct *serial_table[NUM_CHANNELS];static struct termios *serial_termios[NUM_CHANNELS];static struct termios *serial_termios_locked[NUM_CHANNELS];#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 memcpy_fromfs 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[4096]; /* This is cheating */static DECLARE_MUTEX(tmp_buf_sem);static inline int serial_paranoia_check(struct sgi_serial *info, dev_t device, const char *routine){#ifdef SERIAL_PARANOIA_CHECK static const char *badmagic = KERN_WARNING "Warning: bad magic number for serial struct (%d, %d) in %s\n"; static const char *badinfo = KERN_WARNING "Warning: null sgi_serial for (%d, %d) in %s\n"; if (!info) { printk(badinfo, MAJOR(device), MINOR(device), routine); return 1; } if (info->magic != SERIAL_MAGIC) { printk(badmagic, MAJOR(device), MINOR(device), routine); return 1; }#endif return 0;}/* * This is used to figure out the divisor 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 };/* * Reading and writing Zilog8530 registers. The delays are to make this * driver work on the Sun4 which needs a settling delay after each chip * register access, other machines handle this in hardware via auxiliary * flip-flops which implement the settle time we do in software. * * read_zsreg() and write_zsreg() may get called from rs_kgdb_hook() before * interrupts are enabled. Therefore we have to check ioc_iocontrol before we * access it. */static inline unsigned char read_zsreg(struct sgi_zschannel *channel, unsigned char reg){ unsigned char retval; volatile unsigned char junk; udelay(2); channel->control = reg; if (ioc_icontrol) junk = ioc_icontrol->istat0; udelay(1); retval = channel->control; return retval;}static inline void write_zsreg(struct sgi_zschannel *channel, unsigned char reg, unsigned char value){ volatile unsigned char junk; udelay(2); channel->control = reg; if (ioc_icontrol) junk = ioc_icontrol->istat0; udelay(1); channel->control = value; if (ioc_icontrol) junk = ioc_icontrol->istat0; return;}static inline void load_zsregs(struct sgi_zschannel *channel, unsigned char *regs){ ZS_CLEARERR(channel); ZS_CLEARFIFO(channel); /* Load 'em up */ write_zsreg(channel, R4, regs[R4]); write_zsreg(channel, R10, regs[R10]); write_zsreg(channel, R3, regs[R3] & ~RxENABLE); write_zsreg(channel, R5, regs[R5] & ~TxENAB); write_zsreg(channel, R1, regs[R1]); write_zsreg(channel, R9, regs[R9]); write_zsreg(channel, R11, regs[R11]); write_zsreg(channel, R12, regs[R12]); write_zsreg(channel, R13, regs[R13]); write_zsreg(channel, R14, regs[R14]); write_zsreg(channel, R15, regs[R15]); write_zsreg(channel, R3, regs[R3]); write_zsreg(channel, R5, regs[R5]); return;}/* Sets or clears DTR/RTS on the requested line */static inline void zs_rtsdtr(struct sgi_serial *ss, int set){ if(set) { ss->curregs[5] |= (RTS | DTR); ss->pendregs[5] = ss->curregs[5]; write_zsreg(ss->zs_channel, 5, ss->curregs[5]); } else { ss->curregs[5] &= ~(RTS | DTR); ss->pendregs[5] = ss->curregs[5]; write_zsreg(ss->zs_channel, 5, ss->curregs[5]); } return;}static inline void kgdb_chaninit(struct sgi_serial *ss, int intson, int bps){ int brg; if(intson) { kgdb_regs[R1] = INT_ALL_Rx; kgdb_regs[R9] |= MIE; } else { kgdb_regs[R1] = 0; kgdb_regs[R9] &= ~MIE; } brg = BPS_TO_BRG(bps, ZS_CLOCK/ss->clk_divisor); kgdb_regs[R12] = (brg & 255); kgdb_regs[R13] = ((brg >> 8) & 255); load_zsregs(ss->zs_channel, kgdb_regs);}/* Utility routines for the Zilog */static inline int get_zsbaud(struct sgi_serial *ss){ struct sgi_zschannel *channel = ss->zs_channel; int brg; /* The baud rate is split up between two 8-bit registers in * what is termed 'BRG time constant' format in my docs for * the chip, it is a function of the clk rate the chip is * receiving which happens to be constant. */ brg = ((read_zsreg(channel, 13)&0xff) << 8); brg |= (read_zsreg(channel, 12)&0xff); return BRG_TO_BPS(brg, (ZS_CLOCK/(ss->clk_divisor)));}/* * ------------------------------------------------------------ * 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 sgi_serial *info = (struct sgi_serial *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "rs_stop")) return; save_flags(flags); cli(); if (info->curregs[5] & TxENAB) { info->curregs[5] &= ~TxENAB; info->pendregs[5] &= ~TxENAB; write_zsreg(info->zs_channel, 5, info->curregs[5]); } restore_flags(flags);}static void rs_start(struct tty_struct *tty){ struct sgi_serial *info = (struct sgi_serial *)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->curregs[5] & TxENAB)) { info->curregs[5] |= TxENAB; info->pendregs[5] = info->curregs[5]; write_zsreg(info->zs_channel, 5, info->curregs[5]); } restore_flags(flags);}/* Drop into either the boot monitor or kadb upon receiving a break * from keyboard/console input. */static void batten_down_hatches(void){ ArcEnterInteractiveMode();#if 0 /* If we are doing kadb, we call the debugger * else we just drop into the boot monitor. * Note that we must flush the user windows * first before giving up control. */ printk("\n"); if((((unsigned long)linux_dbvec)>=DEBUG_FIRSTVADDR) && (((unsigned long)linux_dbvec)<=DEBUG_LASTVADDR)) sp_enter_debugger(); else prom_halt(); /* XXX We want to notify the keyboard driver that all * XXX keys are in the up state or else weird things * XXX happen... */#endif return;}/* On receive, this clears errors and the receiver interrupts */static inline void rs_recv_clear(struct sgi_zschannel *zsc){ volatile unsigned char junk; udelay(2); zsc->control = ERR_RES; junk = ioc_icontrol->istat0; udelay(2); zsc->control = RES_H_IUS; junk = ioc_icontrol->istat0;}/* * ---------------------------------------------------------------------- * * 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 sgi_serial *info, int event){ info->event |= 1 << event; queue_task(&info->tqueue, &tq_serial); mark_bh(SERIAL_BH);}#ifdef CONFIG_REMOTE_DEBUGextern void set_async_breakpoint(unsigned int epc);#endifstatic _INLINE_ void receive_chars(struct sgi_serial *info, struct pt_regs *regs){ struct tty_struct *tty = info->tty; volatile unsigned char junk; unsigned char ch, stat; udelay(2); ch = info->zs_channel->data; junk = ioc_icontrol->istat0; udelay(2); stat = read_zsreg(info->zs_channel, R1); /* If this is the console keyboard, we need to handle * L1-A's here. */ if(info->is_cons) { if(ch==0) { /* whee, break received */ batten_down_hatches(); rs_recv_clear(info->zs_channel); return; } else if (ch == 1) { show_state(); return; } else if (ch == 2) { show_buffers(); return; } } /* Look for kgdb 'stop' character, consult the gdb documentation * for remote target debugging and arch/sparc/kernel/sparc-stub.c * to see how all this works. */#ifdef CONFIG_REMOTE_DEBUG if((info->kgdb_channel) && (ch =='\003')) { set_async_breakpoint(read_32bit_cp0_register(CP0_EPC)); goto clear_and_exit; }#endif if(!tty) goto clear_and_exit; if (tty->flip.count >= TTY_FLIPBUF_SIZE) queue_task(&tty->flip.tqueue, &tq_timer); tty->flip.count++; if(stat & PAR_ERR) *tty->flip.flag_buf_ptr++ = TTY_PARITY; else if(stat & Rx_OVR) *tty->flip.flag_buf_ptr++ = TTY_OVERRUN; else if(stat & CRC_ERR) *tty->flip.flag_buf_ptr++ = TTY_FRAME; else *tty->flip.flag_buf_ptr++ = 0; /* XXX */ *tty->flip.char_buf_ptr++ = ch; queue_task(&tty->flip.tqueue, &tq_timer);clear_and_exit: rs_recv_clear(info->zs_channel); return;}static _INLINE_ void transmit_chars(struct sgi_serial *info){ volatile unsigned char junk; /* P3: In theory we have to test readiness here because a * serial console can clog the chip through zs_cons_put_char(). * David did not do this. I think he relies on 3-chars FIFO in 8530. * Let's watch for lost _output_ characters. XXX */ /* SGI ADDENDUM: On most SGI machines, the Zilog does possess * a 16 or 17 byte fifo, so no worries. -dm */ if (info->x_char) { /* Send next char */ udelay(2); info->zs_channel->data = info->x_char; junk = ioc_icontrol->istat0; info->x_char = 0; goto clear_and_return; } if((info->xmit_cnt <= 0) || info->tty->stopped) { /* That's peculiar... */ udelay(2); info->zs_channel->control = RES_Tx_P; junk = ioc_icontrol->istat0; goto clear_and_return; } /* Send char */ udelay(2); info->zs_channel->data = info->xmit_buf[info->xmit_tail++]; junk = ioc_icontrol->istat0; info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1); info->xmit_cnt--; if (info->xmit_cnt < WAKEUP_CHARS) rs_sched_event(info, RS_EVENT_WRITE_WAKEUP); if(info->xmit_cnt <= 0) { udelay(2); info->zs_channel->control = RES_Tx_P; junk = ioc_icontrol->istat0; goto clear_and_return; }clear_and_return: /* Clear interrupt */ udelay(2); info->zs_channel->control = RES_H_IUS; junk = ioc_icontrol->istat0; return;}static _INLINE_ void status_handle(struct sgi_serial *info){ volatile unsigned char junk; unsigned char status; /* Get status from Read Register 0 */ udelay(2); status = info->zs_channel->control; junk = ioc_icontrol->istat0; /* Clear status condition... */ udelay(2); info->zs_channel->control = RES_EXT_INT; junk = ioc_icontrol->istat0; /* Clear the interrupt */ udelay(2); info->zs_channel->control = RES_H_IUS; junk = ioc_icontrol->istat0;#if 0 if(status & DCD) { if((info->tty->termios->c_cflag & CRTSCTS) && ((info->curregs[3] & AUTO_ENAB)==0)) { info->curregs[3] |= AUTO_ENAB; info->pendregs[3] |= AUTO_ENAB; write_zsreg(info->zs_channel, 3, info->curregs[3]); } } else { if((info->curregs[3] & AUTO_ENAB)) { info->curregs[3] &= ~AUTO_ENAB; info->pendregs[3] &= ~AUTO_ENAB; write_zsreg(info->zs_channel, 3, info->curregs[3]); } }#endif /* Whee, if this is console input and this is a * 'break asserted' status change interrupt, call * the boot prom. */ if((status & BRK_ABRT) && info->break_abort) batten_down_hatches(); /* XXX Whee, put in a buffer somewhere, the status information * XXX whee whee whee... Where does the information go... */ return;}/* * This is the serial driver's generic interrupt routine */void rs_interrupt(int irq, void *dev_id, struct pt_regs * regs){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -