📄 su.c
字号:
/* $Id: su.c,v 1.1.1.1 2004/02/04 12:56:50 laputa Exp $ * su.c: Small serial driver for keyboard/mouse interface on sparc32/PCI * * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) * Copyright (C) 1998-1999 Pete Zaitcev (zaitcev@yahoo.com) * * This is mainly a variation of drivers/char/serial.c, * credits go to authors mentioned therein. * * Fixed to use tty_get_baud_rate(). * Theodore Ts'o <tytso@mit.edu>, 2001-Oct-12 *//* * Configuration section. */#undef SERIAL_PARANOIA_CHECK#define CONFIG_SERIAL_NOPAUSE_IO /* Unused on sparc */#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#undef SERIAL_DEBUG_THROTTLE#define RS_ISR_PASS_LIMIT 256/* * 0x20 is sun4m thing, Dave Redman heritage. * See arch/sparc/kernel/irq.c. */#define IRQ_4M(n) ((n)|0x20)#if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)#define DBG_CNT(s) \do { \ printk("(%s): [%x] refc=%d, serc=%d, ttyc=%d -> %s\n", \ kdevname(tty->device), (info->flags), serial_refcount, \ info->count,tty->count,s); \} while (0)#else#define DBG_CNT(s)#endif/* * 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/interrupt.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/serial.h>#include <linux/serialP.h>#include <linux/serial_reg.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/bootmem.h>#include <linux/delay.h>#ifdef CONFIG_SERIAL_CONSOLE#include <linux/console.h>#include <linux/major.h>#endif#include <linux/sysrq.h>#include <asm/system.h>#include <asm/oplib.h>#include <asm/io.h>#include <asm/ebus.h>#ifdef CONFIG_SPARC64#include <asm/isa.h>#endif#include <asm/irq.h>#include <asm/uaccess.h>#include <asm/bitops.h>#include "sunserial.h"#include "sunkbd.h"#include "sunmouse.h"/* We are on a NS PC87303 clocked with 24.0 MHz, which results * in a UART clock of 1.8462 MHz. */#define BAUD_BASE (1846200 / 16)#ifdef CONFIG_SERIAL_CONSOLEextern int serial_console;static struct console sercons;int su_serial_console_init(void);#endifenum su_type { SU_PORT_NONE, SU_PORT_MS, SU_PORT_KBD, SU_PORT_PORT };static char *su_typev[] = { "???", "mouse", "kbd", "serial" };#define SU_PROPSIZE 128/* * serial.c saves memory when it allocates async_info upon first open. * We have parts of state structure together because we do call startup * for keyboard and mouse. */struct su_struct { int magic; unsigned long port; int baud_base; int type; /* Hardware type: e.g. 16550 */ int irq; int flags; int line; int cflag; enum su_type port_type; /* Hookup type: e.g. mouse */ int is_console; int port_node; char name[16]; int xmit_fifo_size; int custom_divisor; unsigned short close_delay; unsigned short closing_wait; /* time to wait before closing */ struct tty_struct *tty; int read_status_mask; int ignore_status_mask; int timeout; int quot; int x_char; /* xon/xoff character */ int IER; /* Interrupt Enable Register */ int MCR; /* Modem control register */ unsigned long event; int blocked_open; /* # of blocked opens */ long session; /* Session of opening process */ long pgrp; /* pgrp of opening process */ unsigned char *xmit_buf; int xmit_head; int xmit_tail; int xmit_cnt; struct tq_struct tqueue; wait_queue_head_t open_wait; wait_queue_head_t close_wait; wait_queue_head_t delta_msr_wait; int count; struct async_icount icount; struct termios normal_termios, callout_termios; unsigned long last_active; /* For async_struct, to be */};/* * Scan status structure. * "prop" is a local variable but it eats stack to keep it in each * stack frame of a recursive procedure. */struct su_probe_scan { int msnode, kbnode; /* PROM nodes for mouse and keyboard */ int msx, kbx; /* minors for mouse and keyboard */ int devices; /* scan index */ char prop[SU_PROPSIZE];};static char *serial_name = "PCIO serial driver";static char serial_version[16];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 256static void autoconfig(struct su_struct *info);static void change_speed(struct su_struct *info, struct termios *old);static void su_wait_until_sent(struct tty_struct *tty, int timeout);/* * Here we define the default xmit fifo size used for each type of * UART */static struct serial_uart_config uart_config[] = { { "unknown", 1, 0 }, { "8250", 1, 0 }, { "16450", 1, 0 }, { "16550", 1, 0 }, { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO }, { "cirrus", 1, 0 }, { "ST16650", 1, UART_CLEAR_FIFO |UART_STARTECH }, { "ST16650V2", 32, UART_CLEAR_FIFO | UART_USE_FIFO | UART_STARTECH }, { "TI16750", 64, UART_CLEAR_FIFO | UART_USE_FIFO}, { 0, 0}};#define NR_PORTS 4static struct su_struct su_table[NR_PORTS];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 su_struct *info, kdev_t device, const char *routine){#ifdef SERIAL_PARANOIA_CHECK static const char *badmagic = KERN_WARNING "Warning: bad magic number for serial struct (%s) in %s\n"; static const char *badinfo = KERN_WARNING "Warning: null su_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;}static inlineunsigned int su_inb(struct su_struct *info, unsigned long offset){ return inb(info->port + offset);}static inline voidsu_outb(struct su_struct *info, unsigned long offset, int value){#ifndef __sparc_v9__ /* * MrCoffee has weird schematics: IRQ4 & P10(?) pins of SuperIO are * connected with a gate then go to SlavIO. When IRQ4 goes tristated * gate outputs a logical one. Since we use level triggered interrupts * we have lockup and watchdog reset. We cannot mask IRQ because * keyboard shares IRQ with us (Word has it as Bob Smelik's design). * This problem is similar to what Alpha people suffer, see serial.c. */ if (offset == UART_MCR) value |= UART_MCR_OUT2;#endif outb(value, info->port + offset);}#define serial_in(info, off) su_inb(info, off)#define serial_inp(info, off) su_inb(info, off)#define serial_out(info, off, val) su_outb(info, off, val)#define serial_outp(info, off, val) su_outb(info, off, val)/* * ------------------------------------------------------------ * su_stop() and su_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */static void su_stop(struct tty_struct *tty){ struct su_struct *info = (struct su_struct *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "su_stop")) return; save_flags(flags); cli(); if (info->IER & UART_IER_THRI) { info->IER &= ~UART_IER_THRI; serial_out(info, UART_IER, info->IER); } restore_flags(flags);}static void su_start(struct tty_struct *tty){ struct su_struct *info = (struct su_struct *)tty->driver_data; unsigned long flags; if (serial_paranoia_check(info, tty->device, "su_start")) return; save_flags(flags); cli(); if (info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI)) { info->IER |= UART_IER_THRI; serial_out(info, UART_IER, info->IER); } restore_flags(flags);}/* * ---------------------------------------------------------------------- * * Here starts the interrupt handling routines. All of the following * subroutines are declared as inline and are folded into * su_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 voidsu_sched_event(struct su_struct *info, int event){ info->event |= 1 << event; queue_task(&info->tqueue, &tq_serial); mark_bh(SERIAL_BH);}static voidreceive_kbd_ms_chars(struct su_struct *info, struct pt_regs *regs, int is_brk){ unsigned char status = 0; unsigned char ch; do { ch = serial_inp(info, UART_RX); if (info->port_type == SU_PORT_KBD) { if (ch == SUNKBD_RESET) { l1a_state.kbd_id = 1; l1a_state.l1_down = 0; } else if (l1a_state.kbd_id) { l1a_state.kbd_id = 0; } else if (ch == SUNKBD_L1) { l1a_state.l1_down = 1; } else if (ch == (SUNKBD_L1|SUNKBD_UP)) { l1a_state.l1_down = 0; } else if (ch == SUNKBD_A && l1a_state.l1_down) { /* whee... */ batten_down_hatches(); /* Continue execution... */ l1a_state.l1_down = 0; l1a_state.kbd_id = 0; return; } sunkbd_inchar(ch, regs); } else { sun_mouse_inbyte(ch, is_brk); } status = su_inb(info, UART_LSR); } while (status & UART_LSR_DR);}static voidreceive_serial_chars(struct su_struct *info, int *status, struct pt_regs *regs){ struct tty_struct *tty = info->tty; unsigned char ch; int ignored = 0, saw_console_brk = 0; struct async_icount *icount; icount = &info->icount; do { ch = serial_inp(info, UART_RX); if (info->is_console && (ch == 0 || (*status &UART_LSR_BI))) saw_console_brk = 1; if (tty->flip.count >= TTY_FLIPBUF_SIZE) break; *tty->flip.char_buf_ptr = ch; icount->rx++;#ifdef SERIAL_DEBUG_INTR printk("D%02x:%02x.", ch, *status);#endif *tty->flip.flag_buf_ptr = 0; 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) { if (++ignored > 100) {#ifdef SERIAL_DEBUG_INTR printk("ign100..");#endif break; } 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: *status = serial_inp(info, UART_LSR); } while (*status & UART_LSR_DR);#ifdef SERIAL_DEBUG_INTR printk("E%02x.R%d", *status, tty->flip.count);#endif tty_flip_buffer_push(tty); if (saw_console_brk != 0) batten_down_hatches();}static voidtransmit_chars(struct su_struct *info, int *intr_done){ int count; if (info->x_char) { serial_outp(info, UART_TX, info->x_char); info->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 &= ~UART_IER_THRI; serial_out(info, UART_IER, info->IER); return; } count = info->xmit_fifo_size; do { serial_out(info, UART_TX, info->xmit_buf[info->xmit_tail++]); info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1); info->icount.tx++;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -