/* * linux/drivers/char/pdc_console.c * * 2001, Christoph Plattner * * Driver template was linux's serial.c * */ static char *pdc_drv_version = "0.3"; static char *pdc_drv_revdate = "2001-11-17"; #define AUTHOR "christoph.plattner@gmx.at" #include #include #undef PDC_DRV_DEBUG #undef SERIAL_PARANOIA_CHECK #define CONFIG_SERIAL_NOPAUSE_IO #define SERIAL_DO_RESTART #define PDC_POLL_DELAY (30 * HZ / 1000) /* * End of serial driver configuration section. */ #include #include #include #include #include #include #define LOCAL_VERSTRING "" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_GSC #include #endif extern int pdc_console_poll_key(void *); extern void pdc_outc(unsigned char); static char *pdc_drv_name = "PDC Software Console"; static struct tty_driver pdc_drv_driver; static int pdc_drv_refcount = 0; static struct async_struct *pdc_drv_info; static struct timer_list pdc_drv_timer; /* serial subtype definitions */ #ifndef SERIAL_TYPE_NORMAL #define SERIAL_TYPE_NORMAL 1 #define SERIAL_TYPE_CALLOUT 2 #endif #define NR_PORTS 1 #define PDC_DUMMY_BUF 2048 static struct tty_struct *pdc_drv_table[NR_PORTS]; static struct termios *pdc_drv_termios[NR_PORTS]; static struct termios *pdc_drv_termios_locked[NR_PORTS]; /* * 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; #ifdef DECLARE_MUTEX static DECLARE_MUTEX(tmp_buf_sem); #else static struct semaphore tmp_buf_sem = MUTEX; #endif /* * ------------------------------------------------------------ * pdc_stop() and pdc_start() * * This routines are called before setting or resetting tty->stopped. * They enable or disable transmitter interrupts, as necessary. * ------------------------------------------------------------ */ static void pdc_stop(struct tty_struct *tty) { } static void pdc_start(struct tty_struct *tty) { } /* * ---------------------------------------------------------------------- * * 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 * ----------------------------------------------------------------------- */ static void receive_chars(struct async_struct *info, int *status, struct pt_regs *regs) { struct tty_struct *tty = info->tty; unsigned char ch; int __ch; while (1) { __ch = pdc_console_poll_key(NULL); if (__ch == -1) /* no character available */ break; ch = (unsigned char) ((unsigned) __ch & 0x000000ffu); if (tty->flip.count >= TTY_FLIPBUF_SIZE) continue; *tty->flip.char_buf_ptr = ch; *tty->flip.flag_buf_ptr = 0; tty->flip.flag_buf_ptr++; tty->flip.char_buf_ptr++; tty->flip.count++; } tty_flip_buffer_push(tty); } static void pdc_drv_poll(unsigned long dummy) { struct async_struct *info; int status = 0; info = pdc_drv_info; if (!info || !info->tty || (pdc_drv_refcount == 0)) { /* do nothing */ } else { receive_chars(info, &status, NULL); info->last_active = jiffies; } mod_timer(&pdc_drv_timer, jiffies + PDC_POLL_DELAY); } static void pdc_put_char(struct tty_struct *tty, unsigned char ch) { #ifdef PDC_DRV_DEBUG printk(KERN_NOTICE "[%s] %c return\n", __FUNCTION__, ch); #endif pdc_outc(ch); } static void pdc_flush_chars(struct tty_struct *tty) { /* PCD console always flushed all characters */ #ifdef PDC_DRV_DEBUG printk(KERN_NOTICE "[%s] return\n", __FUNCTION__); #endif /* nothing to do */ } static int pdc_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) { char pdc_tmp_buf[PDC_DUMMY_BUF]; char *pdc_tmp_buf_ptr; int len; int ret = 0; #ifdef PDC_DRV_DEBUG printk(KERN_NOTICE "[%s] entry\n", __FUNCTION__); #endif while (count) { if (count < PDC_DUMMY_BUF) len = count; else len = PDC_DUMMY_BUF; if (from_user) { copy_from_user(pdc_tmp_buf, buf, len); pdc_tmp_buf_ptr = pdc_tmp_buf; } else pdc_tmp_buf_ptr = (char *) buf; while (len) { pdc_outc(*pdc_tmp_buf_ptr); buf++; pdc_tmp_buf_ptr++; ret++; count--; len--; } } #ifdef PDC_DRV_DEBUG printk(KERN_NOTICE "[%s] return\n", __FUNCTION__); #endif return ret; } static int pdc_write_room(struct tty_struct *tty) { #ifdef PDC_DRV_DEBUG printk(KERN_NOTICE "[%s] entry\n", __FUNCTION__); #endif return PDC_DUMMY_BUF; } static int pdc_chars_in_buffer(struct tty_struct *tty) { #ifdef PDC_DRV_DEBUG printk(KERN_NOTICE "[%s] entry\n", __FUNCTION__); #endif return 0; /* no characters in buffer, always flushed ! */ } static void pdc_flush_buffer(struct tty_struct *tty) { #ifdef PDC_DRV_DEBUG printk(KERN_NOTICE "[%s] return\n", __FUNCTION__); #endif } /* * This function is used to send a high-priority XON/XOFF character to * the device */ static void pdc_send_xchar(struct tty_struct *tty, char ch) { } /* * ------------------------------------------------------------ * pdc_throttle() * * This routine is called by the upper-layer tty layer to signal that * incoming characters should be throttled. * ------------------------------------------------------------ */ static void pdc_throttle(struct tty_struct *tty) { } static void pdc_unthrottle(struct tty_struct *tty) { } /* * ------------------------------------------------------------ * pdc_ioctl() and friends * ------------------------------------------------------------ */ static void pdc_break(struct tty_struct *tty, int break_state) { } static int get_serial_info(struct async_struct * info, struct serial_struct * retinfo) { struct serial_struct tmp; if (!retinfo) return -EFAULT; memset(&tmp, 0, sizeof(tmp)); tmp.line = info->line; tmp.port = info->line; tmp.flags = info->flags; tmp.close_delay = info->close_delay; return copy_to_user(retinfo,&tmp,sizeof(*retinfo)) ? -EFAULT : 0; } static int get_modem_info(struct async_struct * info, unsigned int *value) { unsigned int result = TIOCM_DTR|TIOCM_CAR|TIOCM_CTS|TIOCM_RTS; return copy_to_user(value, &result, sizeof(int)) ? -EFAULT : 0; } static int get_lsr_info(struct async_struct * info, unsigned int *value) { unsigned int result = TIOCSER_TEMT; return copy_to_user(value, &result, sizeof(int)) ? -EFAULT : 0; } static int pdc_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) { struct async_struct *info = (struct async_struct *) tty->driver_data; 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: return get_modem_info(info, (unsigned int *) arg); case TIOCMBIS: case TIOCMBIC: case TIOCMSET: return 0; case TIOCGSERIAL: return get_serial_info(info, (struct serial_struct *) arg); case TIOCSSERIAL: return 0; case TIOCSERCONFIG: return 0; case TIOCSERGETLSR: /* Get line status register */ return get_lsr_info(info, (unsigned int *) arg); case TIOCSERGSTRUCT: if (copy_to_user((struct async_struct *) arg, info, sizeof (struct async_struct))) return -EFAULT; return 0; case TIOCMIWAIT: return 0; case TIOCGICOUNT: return 0; case TIOCSERGWILD: case TIOCSERSWILD: /* "setserial -W" is called in Debian boot */ printk("TIOCSER?WILD ioctl obsolete, ignored.\n"); return 0; default: return -ENOIOCTLCMD; } return 0; } static void pdc_set_termios(struct tty_struct *tty, struct termios *old_termios) { #if 0 /* XXX CP, has to be checked, if there is stuff to do */ struct async_struct *info = (struct async_struct *) tty->driver_data; unsigned long flags; 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; #if 0 change_speed(info, old_termios); #endif /* Handle turning off CRTSCTS */ if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) { tty->hw_stopped = 0; pdc_start(tty); } #endif } /* * ------------------------------------------------------------ * pdc_close() * * This routine is called when the serial port gets closed. First, we * wait for the last remaining data to be sent. Then, we unlink its * async structure from the interrupt chain if necessary, and we free * that IRQ if nothing is left in the chain. * ------------------------------------------------------------ */ static void pdc_close(struct tty_struct *tty, struct file *filp) { struct async_struct *info = (struct async_struct *) tty->driver_data; #ifdef PDC_DEBUG_OPEN printk("pdc_close ttyB%d, count = %d\n", info->line, state->count); #endif pdc_drv_refcount--; if (pdc_drv_refcount > 0) return; info->flags |= ASYNC_CLOSING; /* * Save the termios structure, since this port may have * separate termios for callout and dialin. */ if (info->flags & ASYNC_NORMAL_ACTIVE) info->state->normal_termios = *tty->termios; if (info->flags & ASYNC_CALLOUT_ACTIVE) info->state->callout_termios = *tty->termios; /* * At this point we stop accepting input. To do this, we * disable the receive line status interrupts, and tell the * interrupt driver to stop checking the data ready bit in the * line status register. */ /* XXX CP: make mask for receive !!! */ if (tty->driver.flush_buffer) tty->driver.flush_buffer(tty); tty_ldisc_flush(tty); tty->closing = 0; info->event = 0; info->tty = 0; pdc_drv_info = NULL; if (info->blocked_open) { if (info->close_delay) { set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(info->close_delay); } wake_up_interruptible(&info->open_wait); } info->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CALLOUT_ACTIVE | ASYNC_CLOSING); wake_up_interruptible(&info->close_wait); MOD_DEC_USE_COUNT; } /* * pdc_wait_until_sent() --- wait until the transmitter is empty */ static void pdc_wait_until_sent(struct tty_struct *tty, int timeout) { /* we always send immideate */ } /* * pdc_hangup() --- called by tty_hangup() when a hangup is signaled. */ static void pdc_hangup(struct tty_struct *tty) { } /* * ------------------------------------------------------------ * pdc_open() and friends * ------------------------------------------------------------ */ static int get_async_struct(int line, struct async_struct **ret_info) { struct async_struct *info; info = kmalloc(sizeof (struct async_struct), GFP_KERNEL); if (!info) { return -ENOMEM; } memset(info, 0, sizeof (struct async_struct)); init_waitqueue_head(&info->open_wait); init_waitqueue_head(&info->close_wait); init_waitqueue_head(&info->delta_msr_wait); info->magic = SERIAL_MAGIC; info->port = 0; info->flags = 0; info->io_type = 0; info->iomem_base = 0; info->iomem_reg_shift = 0; info->xmit_fifo_size = PDC_DUMMY_BUF; info->line = line; info->tqueue.routine = NULL; info->tqueue.data = info; info->state = NULL; *ret_info = info; return 0; } /* * This routine is called whenever a serial port is opened. It * enables interrupts for a serial port, linking in its async structure into * the IRQ chain. It also performs the serial-specific * initialization for the tty structure. */ static int pdc_open(struct tty_struct *tty, struct file *filp) { struct async_struct *info; int retval, line; unsigned long page; MOD_INC_USE_COUNT; line = MINOR(tty->device) - tty->driver.minor_start; if ((line < 0) || (line >= NR_PORTS)) { MOD_DEC_USE_COUNT; return -ENODEV; } retval = get_async_struct(line, &info); if (retval) { MOD_DEC_USE_COUNT; return retval; } tty->driver_data = info; info->tty = tty; pdc_drv_info = info; #ifdef PDC_DEBUG_OPEN printk("pdc_open %s%d, count = %d\n", tty->driver.name, info->line, info->state->count); #endif info->tty->low_latency = 0; if (!tmp_buf) { page = get_zeroed_page(GFP_KERNEL); if (!page) { MOD_DEC_USE_COUNT; return -ENOMEM; } if (tmp_buf) free_page(page); else tmp_buf = (unsigned char *) page; } info->session = current->session; info->pgrp = current->pgrp; #ifdef PDC_DEBUG_OPEN printk("pdc_open ttyB%d successful...", info->line); #endif pdc_drv_refcount++; return 0; } /* * --------------------------------------------------------------------- * pdc_init() and friends * * pdc_init() is called at boot-time to initialize the pdc driver. * --------------------------------------------------------------------- */ static void show_pdc_drv_version(void) { printk(KERN_INFO "%s version %s%s (%s), %s\n", pdc_drv_name, pdc_drv_version, LOCAL_VERSTRING, pdc_drv_revdate, AUTHOR); } /* * The serial driver boot-time initialization code! */ static int __init pdc_drv_init(void) { init_timer(&pdc_drv_timer); pdc_drv_timer.function = pdc_drv_poll; mod_timer(&pdc_drv_timer, jiffies + PDC_POLL_DELAY); show_pdc_drv_version(); /* Initialize the tty_driver structure */ memset(&pdc_drv_driver, 0, sizeof (struct tty_driver)); pdc_drv_driver.magic = TTY_DRIVER_MAGIC; pdc_drv_driver.driver_name = "pdc_console"; #ifdef CONFIG_DEVFS_FS pdc_drv_driver.name = "ttb/%d"; #else pdc_drv_driver.name = "ttyB"; #endif pdc_drv_driver.major = MUX_MAJOR; pdc_drv_driver.minor_start = 0; pdc_drv_driver.num = NR_PORTS; pdc_drv_driver.type = TTY_DRIVER_TYPE_SERIAL; pdc_drv_driver.subtype = SERIAL_TYPE_NORMAL; pdc_drv_driver.init_termios = tty_std_termios; pdc_drv_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; pdc_drv_driver.flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS; pdc_drv_driver.refcount = &pdc_drv_refcount; pdc_drv_driver.table = pdc_drv_table; pdc_drv_driver.termios = pdc_drv_termios; pdc_drv_driver.termios_locked = pdc_drv_termios_locked; pdc_drv_driver.open = pdc_open; pdc_drv_driver.close = pdc_close; pdc_drv_driver.write = pdc_write; pdc_drv_driver.put_char = pdc_put_char; pdc_drv_driver.flush_chars = pdc_flush_chars; pdc_drv_driver.write_room = pdc_write_room; pdc_drv_driver.chars_in_buffer = pdc_chars_in_buffer; pdc_drv_driver.flush_buffer = pdc_flush_buffer; pdc_drv_driver.ioctl = pdc_ioctl; pdc_drv_driver.throttle = pdc_throttle; pdc_drv_driver.unthrottle = pdc_unthrottle; pdc_drv_driver.set_termios = pdc_set_termios; pdc_drv_driver.stop = pdc_stop; pdc_drv_driver.start = pdc_start; pdc_drv_driver.hangup = pdc_hangup; pdc_drv_driver.break_ctl = pdc_break; pdc_drv_driver.send_xchar = pdc_send_xchar; pdc_drv_driver.wait_until_sent = pdc_wait_until_sent; pdc_drv_driver.read_proc = NULL; if (tty_register_driver(&pdc_drv_driver)) panic("Couldn't register pdc_console driver\n"); return 0; } static void __exit pdc_fini(void) { int e1; if ((e1 = tty_unregister_driver(&pdc_drv_driver))) printk("pdc_console: failed to unregister pdc_drv driver (%d)\n", e1); } module_init(pdc_drv_init); module_exit(pdc_fini); MODULE_DESCRIPTION("PDC Software Console"); MODULE_AUTHOR(AUTHOR);