📄 simserial.c
字号:
/* * Simulated Serial Driver (fake serial) * * This driver is mostly used for bringup purposes and will go away. * It has a strong dependency on the system console. All outputs * are rerouted to the same facility as the one used by printk which, in our * case means sys_sim.c console (goes via the simulator). The code hereafter * is completely leveraged from the serial.c driver. * * Copyright (C) 1999-2000, 2002-2003 Hewlett-Packard Co * Stephane Eranian <eranian@hpl.hp.com> * David Mosberger-Tang <davidm@hpl.hp.com> * * 02/04/00 D. Mosberger Merged in serial.c bug fixes in rs_close(). * 02/25/00 D. Mosberger Synced up with 2.3.99pre-5 version of serial.c. * 07/30/02 D. Mosberger Replace sti()/cli() with explicit spinlocks & local irq masking */#include <linux/init.h>#include <linux/errno.h>#include <linux/sched.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/major.h>#include <linux/fcntl.h>#include <linux/mm.h>#include <linux/slab.h>#include <linux/capability.h>#include <linux/console.h>#include <linux/module.h>#include <linux/serial.h>#include <linux/serialP.h>#include <linux/sysrq.h>#include <asm/irq.h>#include <asm/hw_irq.h>#include <asm/uaccess.h>#undef SIMSERIAL_DEBUG /* define this to get some debug information */#define KEYBOARD_INTR 3 /* must match with simulator! */#define NR_PORTS 1 /* only one port for now */#define IRQ_T(info) ((info->flags & ASYNC_SHARE_IRQ) ? IRQF_SHARED : IRQF_DISABLED)#define SSC_GETCHAR 21extern long ia64_ssc (long, long, long, long, int);extern void ia64_ssc_connect_irq (long intr, long irq);static char *serial_name = "SimSerial driver";static char *serial_version = "0.6";/* * This has been extracted from asm/serial.h. We need one eventually but * I don't know exactly what we're going to put in it so just fake one * for now. */#define BASE_BAUD ( 1843200 / 16 )#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST)/* * Most of the values here are meaningless to this particular driver. * However some values must be preserved for the code (leveraged from serial.c * to work correctly). * port must not be 0 * type must not be UNKNOWN * So I picked arbitrary (guess from where?) values instead */static struct serial_state rs_table[NR_PORTS]={ /* UART CLK PORT IRQ FLAGS */ { 0, BASE_BAUD, 0x3F8, 0, STD_COM_FLAGS,0,PORT_16550 } /* ttyS0 */};/* * Just for the fun of it ! */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}, { NULL, 0}};struct tty_driver *hp_simserial_driver;static struct async_struct *IRQ_ports[NR_IRQS];static struct console *console;static unsigned char *tmp_buf;extern struct console *console_drivers; /* from kernel/printk.c *//* * ------------------------------------------------------------ * 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){#ifdef SIMSERIAL_DEBUG printk("rs_stop: tty->stopped=%d tty->hw_stopped=%d tty->flow_stopped=%d\n", tty->stopped, tty->hw_stopped, tty->flow_stopped);#endif}static void rs_start(struct tty_struct *tty){#ifdef SIMSERIAL_DEBUG printk("rs_start: tty->stopped=%d tty->hw_stopped=%d tty->flow_stopped=%d\n", tty->stopped, tty->hw_stopped, tty->flow_stopped);#endif}static void receive_chars(struct tty_struct *tty){ unsigned char ch; static unsigned char seen_esc = 0; while ( (ch = ia64_ssc(0, 0, 0, 0, SSC_GETCHAR)) ) { if ( ch == 27 && seen_esc == 0 ) { seen_esc = 1; continue; } else { if ( seen_esc==1 && ch == 'O' ) { seen_esc = 2; continue; } else if ( seen_esc == 2 ) { if ( ch == 'P' ) /* F1 */ show_state();#ifdef CONFIG_MAGIC_SYSRQ if ( ch == 'S' ) { /* F4 */ do ch = ia64_ssc(0, 0, 0, 0, SSC_GETCHAR); while (!ch); handle_sysrq(ch, NULL); }#endif seen_esc = 0; continue; } } seen_esc = 0; if (tty_insert_flip_char(tty, ch, TTY_NORMAL) == 0) break; } tty_flip_buffer_push(tty);}/* * This is the serial driver's interrupt routine for a single port */static irqreturn_t rs_interrupt_single(int irq, void *dev_id){ struct async_struct * info; /* * I don't know exactly why they don't use the dev_id opaque data * pointer instead of this extra lookup table */ info = IRQ_ports[irq]; if (!info || !info->tty) { printk(KERN_INFO "simrs_interrupt_single: info|tty=0 info=%p problem\n", info); return IRQ_NONE; } /* * pretty simple in our case, because we only get interrupts * on inbound traffic */ receive_chars(info->tty); return IRQ_HANDLED;}/* * ------------------------------------------------------------------- * Here ends the serial interrupt routines. * ------------------------------------------------------------------- */#if 0/* * not really used in our situation so keep them commented out for now */static DECLARE_TASK_QUEUE(tq_serial); /* used to be at the top of the file */static void do_serial_bh(void){ run_task_queue(&tq_serial); printk(KERN_ERR "do_serial_bh: called\n");}#endifstatic void do_softint(struct work_struct *private_){ printk(KERN_ERR "simserial: do_softint called\n");}static void rs_put_char(struct tty_struct *tty, unsigned char ch){ struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; if (!tty || !info->xmit.buf) return; local_irq_save(flags); if (CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE) == 0) { local_irq_restore(flags); return; } info->xmit.buf[info->xmit.head] = ch; info->xmit.head = (info->xmit.head + 1) & (SERIAL_XMIT_SIZE-1); local_irq_restore(flags);}static void transmit_chars(struct async_struct *info, int *intr_done){ int count; unsigned long flags; local_irq_save(flags); if (info->x_char) { char c = info->x_char; console->write(console, &c, 1); info->state->icount.tx++; info->x_char = 0; goto out; } if (info->xmit.head == info->xmit.tail || info->tty->stopped || info->tty->hw_stopped) {#ifdef SIMSERIAL_DEBUG printk("transmit_chars: head=%d, tail=%d, stopped=%d\n", info->xmit.head, info->xmit.tail, info->tty->stopped);#endif goto out; } /* * We removed the loop and try to do it in to chunks. We need * 2 operations maximum because it's a ring buffer. * * First from current to tail if possible. * Then from the beginning of the buffer until necessary */ count = min(CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE), SERIAL_XMIT_SIZE - info->xmit.tail); console->write(console, info->xmit.buf+info->xmit.tail, count); info->xmit.tail = (info->xmit.tail+count) & (SERIAL_XMIT_SIZE-1); /* * We have more at the beginning of the buffer */ count = CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); if (count) { console->write(console, info->xmit.buf, count); info->xmit.tail += count; }out: local_irq_restore(flags);}static void rs_flush_chars(struct tty_struct *tty){ struct async_struct *info = (struct async_struct *)tty->driver_data; if (info->xmit.head == info->xmit.tail || tty->stopped || tty->hw_stopped || !info->xmit.buf) return; transmit_chars(info, NULL);}static int rs_write(struct tty_struct * tty, const unsigned char *buf, int count){ int c, ret = 0; struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; if (!tty || !info->xmit.buf || !tmp_buf) return 0; local_irq_save(flags); while (1) { c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, SERIAL_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) & (SERIAL_XMIT_SIZE-1)); buf += c; count -= c; ret += c; } local_irq_restore(flags); /* * Hey, we transmit directly from here in our case */ if (CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE) && !tty->stopped && !tty->hw_stopped) { transmit_chars(info, NULL); } return ret;}static int rs_write_room(struct tty_struct *tty){ struct async_struct *info = (struct async_struct *)tty->driver_data; return CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE);}static int rs_chars_in_buffer(struct tty_struct *tty){ struct async_struct *info = (struct async_struct *)tty->driver_data; return CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE);}static void rs_flush_buffer(struct tty_struct *tty){ struct async_struct *info = (struct async_struct *)tty->driver_data; unsigned long flags; local_irq_save(flags); info->xmit.head = info->xmit.tail = 0; local_irq_restore(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 rs_send_xchar(struct tty_struct *tty, char ch){ struct async_struct *info = (struct async_struct *)tty->driver_data; info->x_char = ch; if (ch) { /* * I guess we could call console->write() directly but * let's do that for now. */ transmit_chars(info, NULL); }}/* * ------------------------------------------------------------ * rs_throttle() * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. * ------------------------------------------------------------ */static void rs_throttle(struct tty_struct * tty){ if (I_IXOFF(tty)) rs_send_xchar(tty, STOP_CHAR(tty)); printk(KERN_INFO "simrs_throttle called\n");}static void rs_unthrottle(struct tty_struct * tty){ struct async_struct *info = (struct async_struct *)tty->driver_data; if (I_IXOFF(tty)) { if (info->x_char) info->x_char = 0; else rs_send_xchar(tty, START_CHAR(tty)); } printk(KERN_INFO "simrs_unthrottle called\n");}/* * rs_break() --- routine which turns the break handling on or off */static void rs_break(struct tty_struct *tty, int break_state){}static int rs_ioctl(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg){ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { if (tty->flags & (1 << TTY_IO_ERROR)) return -EIO; } switch (cmd) { case TIOCMGET: printk(KERN_INFO "rs_ioctl: TIOCMGET called\n"); return -EINVAL; case TIOCMBIS: case TIOCMBIC: case TIOCMSET: printk(KERN_INFO "rs_ioctl: TIOCMBIS/BIC/SET called\n"); return -EINVAL; case TIOCGSERIAL: printk(KERN_INFO "simrs_ioctl TIOCGSERIAL called\n"); return 0; case TIOCSSERIAL: printk(KERN_INFO "simrs_ioctl TIOCSSERIAL called\n"); return 0; case TIOCSERCONFIG: printk(KERN_INFO "rs_ioctl: TIOCSERCONFIG called\n"); return -EINVAL; case TIOCSERGETLSR: /* Get line status register */ printk(KERN_INFO "rs_ioctl: TIOCSERGETLSR called\n"); return -EINVAL; case TIOCSERGSTRUCT: printk(KERN_INFO "rs_ioctl: TIOCSERGSTRUCT called\n");#if 0 if (copy_to_user((struct async_struct *) arg, info, sizeof(struct async_struct))) return -EFAULT;#endif return 0; /* * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change * - mask passed in arg for lines of interest * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) * Caller should use TIOCGICOUNT to see which one it was */ case TIOCMIWAIT: printk(KERN_INFO "rs_ioctl: TIOCMIWAIT: called\n"); return 0; /* * Get counter of input serial line interrupts (DCD,RI,DSR,CTS) * Return: write counters to the user passed counter struct * NB: both 1->0 and 0->1 transitions are counted except for * RI where only 0->1 is counted. */ case TIOCGICOUNT: printk(KERN_INFO "rs_ioctl: TIOCGICOUNT called\n"); return 0; case TIOCSERGWILD: case TIOCSERSWILD: /* "setserial -W" is called in Debian boot */ printk (KERN_INFO "TIOCSER?WILD ioctl obsolete, ignored.\n"); return 0; default: return -ENOIOCTLCMD; } return 0;}#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))static void rs_set_termios(struct tty_struct *tty, struct ktermios *old_termios){ unsigned int cflag = tty->termios->c_cflag; if ( (cflag == old_termios->c_cflag) && ( RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) return; /* Handle turning off CRTSCTS */ if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) { tty->hw_stopped = 0; rs_start(tty); }}/* * 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 shutdown(struct async_struct * info){ unsigned long flags; struct serial_state *state; int retval;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -