/* * interface to user space for the gigaset driver * * Copyright (c) 2004 by Hansjoerg Lipp * * ===================================================================== * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * ===================================================================== */ #include "gigaset.h" #include #include #include /*** our ioctls ***/ static int if_lock(struct cardstate *cs, int *arg) { int cmd = *arg; gig_dbg(DEBUG_IF, "%u: if_lock (%d)", cs->minor_index, cmd); if (cmd > 1) return -EINVAL; if (cmd < 0) { *arg = atomic_read(&cs->mstate) == MS_LOCKED; //FIXME remove? return 0; } if (!cmd && atomic_read(&cs->mstate) == MS_LOCKED && cs->connected) { cs->ops->set_modem_ctrl(cs, 0, TIOCM_DTR|TIOCM_RTS); cs->ops->baud_rate(cs, B115200); cs->ops->set_line_ctrl(cs, CS8); cs->control_state = TIOCM_DTR|TIOCM_RTS; } cs->waiting = 1; if (!gigaset_add_event(cs, &cs->at_state, EV_IF_LOCK, NULL, cmd, NULL)) { cs->waiting = 0; return -ENOMEM; } gig_dbg(DEBUG_CMD, "scheduling IF_LOCK"); gigaset_schedule_event(cs); wait_event(cs->waitqueue, !cs->waiting); if (cs->cmd_result >= 0) { *arg = cs->cmd_result; return 0; } return cs->cmd_result; } static int if_version(struct cardstate *cs, unsigned arg[4]) { static const unsigned version[4] = GIG_VERSION; static const unsigned compat[4] = GIG_COMPAT; unsigned cmd = arg[0]; gig_dbg(DEBUG_IF, "%u: if_version (%d)", cs->minor_index, cmd); switch (cmd) { case GIGVER_DRIVER: memcpy(arg, version, sizeof version); return 0; case GIGVER_COMPAT: memcpy(arg, compat, sizeof compat); return 0; case GIGVER_FWBASE: cs->waiting = 1; if (!gigaset_add_event(cs, &cs->at_state, EV_IF_VER, NULL, 0, arg)) { cs->waiting = 0; return -ENOMEM; } gig_dbg(DEBUG_CMD, "scheduling IF_VER"); gigaset_schedule_event(cs); wait_event(cs->waitqueue, !cs->waiting); if (cs->cmd_result >= 0) return 0; return cs->cmd_result; default: return -EINVAL; } } static int if_config(struct cardstate *cs, int *arg) { gig_dbg(DEBUG_IF, "%u: if_config (%d)", cs->minor_index, *arg); if (*arg != 1) return -EINVAL; if (atomic_read(&cs->mstate) != MS_LOCKED) return -EBUSY; if (!cs->connected) { err("not connected!"); return -ENODEV; } *arg = 0; return gigaset_enterconfigmode(cs); } /*** the terminal driver ***/ /* stolen from usbserial and some other tty drivers */ static int if_open(struct tty_struct *tty, struct file *filp); static void if_close(struct tty_struct *tty, struct file *filp); static int if_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg); static int if_write_room(struct tty_struct *tty); static int if_chars_in_buffer(struct tty_struct *tty); static void if_throttle(struct tty_struct *tty); static void if_unthrottle(struct tty_struct *tty); static void if_set_termios(struct tty_struct *tty, struct ktermios *old); static int if_tiocmget(struct tty_struct *tty, struct file *file); static int if_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear); static int if_write(struct tty_struct *tty, const unsigned char *buf, int count); static const struct tty_operations if_ops = { .open = if_open, .close = if_close, .ioctl = if_ioctl, .write = if_write, .write_room = if_write_room, .chars_in_buffer = if_chars_in_buffer, .set_termios = if_set_termios, .throttle = if_throttle, .unthrottle = if_unthrottle, #if 0 .break_ctl = serial_break, #endif .tiocmget = if_tiocmget, .tiocmset = if_tiocmset, }; static int if_open(struct tty_struct *tty, struct file *filp) { struct cardstate *cs; unsigned long flags; gig_dbg(DEBUG_IF, "%d+%d: %s()", tty->driver->minor_start, tty->index, __func__); tty->driver_data = NULL; cs = gigaset_get_cs_by_tty(tty); if (!cs) return -ENODEV; if (mutex_lock_interruptible(&cs->mutex)) return -ERESTARTSYS; // FIXME -EINTR? tty->driver_data = cs; ++cs->open_count; if (cs->open_count == 1) { spin_lock_irqsave(&cs->lock, flags); cs->tty = tty; spin_unlock_irqrestore(&cs->lock, flags); tty->low_latency = 1; //FIXME test } mutex_unlock(&cs->mutex); return 0; } static void if_close(struct tty_struct *tty, struct file *filp) { struct cardstate *cs; unsigned long flags; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); mutex_lock(&cs->mutex); if (!cs->open_count) warn("%s: device not opened", __func__); else { if (!--cs->open_count) { spin_lock_irqsave(&cs->lock, flags); cs->tty = NULL; spin_unlock_irqrestore(&cs->lock, flags); } } mutex_unlock(&cs->mutex); } static int if_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) { struct cardstate *cs; int retval = -ENODEV; int int_arg; unsigned char buf[6]; unsigned version[4]; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return -ENODEV; } gig_dbg(DEBUG_IF, "%u: %s(0x%x)", cs->minor_index, __func__, cmd); if (mutex_lock_interruptible(&cs->mutex)) return -ERESTARTSYS; // FIXME -EINTR? if (!cs->open_count) warn("%s: device not opened", __func__); else { retval = 0; switch (cmd) { case GIGASET_REDIR: retval = get_user(int_arg, (int __user *) arg); if (retval >= 0) retval = if_lock(cs, &int_arg); if (retval >= 0) retval = put_user(int_arg, (int __user *) arg); break; case GIGASET_CONFIG: retval = get_user(int_arg, (int __user *) arg); if (retval >= 0) retval = if_config(cs, &int_arg); if (retval >= 0) retval = put_user(int_arg, (int __user *) arg); break; case GIGASET_BRKCHARS: //FIXME test if MS_LOCKED if (!cs->connected) { gig_dbg(DEBUG_ANY, "can't communicate with unplugged device"); retval = -ENODEV; break; } retval = copy_from_user(&buf, (const unsigned char __user *) arg, 6) ? -EFAULT : 0; if (retval >= 0) { gigaset_dbg_buffer(DEBUG_IF, "GIGASET_BRKCHARS", 6, (const unsigned char *) arg); retval = cs->ops->brkchars(cs, buf); } break; case GIGASET_VERSION: retval = copy_from_user(version, (unsigned __user *) arg, sizeof version) ? -EFAULT : 0; if (retval >= 0) retval = if_version(cs, version); if (retval >= 0) retval = copy_to_user((unsigned __user *) arg, version, sizeof version) ? -EFAULT : 0; break; default: gig_dbg(DEBUG_ANY, "%s: arg not supported - 0x%04x", __func__, cmd); retval = -ENOIOCTLCMD; } } mutex_unlock(&cs->mutex); return retval; } static int if_tiocmget(struct tty_struct *tty, struct file *file) { struct cardstate *cs; int retval; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return -ENODEV; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); if (mutex_lock_interruptible(&cs->mutex)) return -ERESTARTSYS; // FIXME -EINTR? // FIXME read from device? retval = cs->control_state & (TIOCM_RTS|TIOCM_DTR); mutex_unlock(&cs->mutex); return retval; } static int if_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear) { struct cardstate *cs; int retval; unsigned mc; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return -ENODEV; } gig_dbg(DEBUG_IF, "%u: %s(0x%x, 0x%x)", cs->minor_index, __func__, set, clear); if (mutex_lock_interruptible(&cs->mutex)) return -ERESTARTSYS; // FIXME -EINTR? if (!cs->connected) { gig_dbg(DEBUG_ANY, "can't communicate with unplugged device"); retval = -ENODEV; } else { mc = (cs->control_state | set) & ~clear & (TIOCM_RTS|TIOCM_DTR); retval = cs->ops->set_modem_ctrl(cs, cs->control_state, mc); cs->control_state = mc; } mutex_unlock(&cs->mutex); return retval; } static int if_write(struct tty_struct *tty, const unsigned char *buf, int count) { struct cardstate *cs; int retval = -ENODEV; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return -ENODEV; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); if (mutex_lock_interruptible(&cs->mutex)) return -ERESTARTSYS; // FIXME -EINTR? if (!cs->open_count) warn("%s: device not opened", __func__); else if (atomic_read(&cs->mstate) != MS_LOCKED) { warn("can't write to unlocked device"); retval = -EBUSY; } else if (!cs->connected) { gig_dbg(DEBUG_ANY, "can't write to unplugged device"); retval = -EBUSY; //FIXME } else { retval = cs->ops->write_cmd(cs, buf, count, &cs->if_wake_tasklet); } mutex_unlock(&cs->mutex); return retval; } static int if_write_room(struct tty_struct *tty) { struct cardstate *cs; int retval = -ENODEV; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return -ENODEV; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); if (mutex_lock_interruptible(&cs->mutex)) return -ERESTARTSYS; // FIXME -EINTR? if (!cs->open_count) warn("%s: device not opened", __func__); else if (atomic_read(&cs->mstate) != MS_LOCKED) { warn("can't write to unlocked device"); retval = -EBUSY; //FIXME } else if (!cs->connected) { gig_dbg(DEBUG_ANY, "can't write to unplugged device"); retval = -EBUSY; //FIXME } else retval = cs->ops->write_room(cs); mutex_unlock(&cs->mutex); return retval; } static int if_chars_in_buffer(struct tty_struct *tty) { struct cardstate *cs; int retval = -ENODEV; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return -ENODEV; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); if (mutex_lock_interruptible(&cs->mutex)) return -ERESTARTSYS; // FIXME -EINTR? if (!cs->open_count) warn("%s: device not opened", __func__); else if (atomic_read(&cs->mstate) != MS_LOCKED) { warn("can't write to unlocked device"); retval = -EBUSY; } else if (!cs->connected) { gig_dbg(DEBUG_ANY, "can't write to unplugged device"); retval = -EBUSY; //FIXME } else retval = cs->ops->chars_in_buffer(cs); mutex_unlock(&cs->mutex); return retval; } static void if_throttle(struct tty_struct *tty) { struct cardstate *cs; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); mutex_lock(&cs->mutex); if (!cs->open_count) warn("%s: device not opened", __func__); else { //FIXME } mutex_unlock(&cs->mutex); } static void if_unthrottle(struct tty_struct *tty) { struct cardstate *cs; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); mutex_lock(&cs->mutex); if (!cs->open_count) warn("%s: device not opened", __func__); else { //FIXME } mutex_unlock(&cs->mutex); } static void if_set_termios(struct tty_struct *tty, struct ktermios *old) { struct cardstate *cs; unsigned int iflag; unsigned int cflag; unsigned int old_cflag; unsigned int control_state, new_state; cs = (struct cardstate *) tty->driver_data; if (!cs) { err("cs==NULL in %s", __func__); return; } gig_dbg(DEBUG_IF, "%u: %s()", cs->minor_index, __func__); mutex_lock(&cs->mutex); if (!cs->open_count) { warn("%s: device not opened", __func__); goto out; } if (!cs->connected) { gig_dbg(DEBUG_ANY, "can't communicate with unplugged device"); goto out; } // stolen from mct_u232.c iflag = tty->termios->c_iflag; cflag = tty->termios->c_cflag; old_cflag = old ? old->c_cflag : cflag; //FIXME? gig_dbg(DEBUG_IF, "%u: iflag %x cflag %x old %x", cs->minor_index, iflag, cflag, old_cflag); /* get a local copy of the current port settings */ control_state = cs->control_state; /* * Update baud rate. * Do not attempt to cache old rates and skip settings, * disconnects screw such tricks up completely. * Premature optimization is the root of all evil. */ /* reassert DTR and (maybe) RTS on transition from B0 */ if ((old_cflag & CBAUD) == B0) { new_state = control_state | TIOCM_DTR; /* don't set RTS if using hardware flow control */ if (!(old_cflag & CRTSCTS)) new_state |= TIOCM_RTS; gig_dbg(DEBUG_IF, "%u: from B0 - set DTR%s", cs->minor_index, (new_state & TIOCM_RTS) ? " only" : "/RTS"); cs->ops->set_modem_ctrl(cs, control_state, new_state); control_state = new_state; } cs->ops->baud_rate(cs, cflag & CBAUD); if ((cflag & CBAUD) == B0) { /* Drop RTS and DTR */ gig_dbg(DEBUG_IF, "%u: to B0 - drop DTR/RTS", cs->minor_index); new_state = control_state & ~(TIOCM_DTR | TIOCM_RTS); cs->ops->set_modem_ctrl(cs, control_state, new_state); control_state = new_state; } /* * Update line control register (LCR) */ cs->ops->set_line_ctrl(cs, cflag); #if 0 //FIXME this hangs M101 [ts 2005-03-09] //FIXME do we need this? /* * Set flow control: well, I do not really now how to handle DTR/RTS. * Just do what we have seen with SniffUSB on Win98. */ /* Drop DTR/RTS if no flow control otherwise assert */ gig_dbg(DEBUG_IF, "%u: control_state %x", cs->minor_index, control_state); new_state = control_state; if ((iflag & IXOFF) || (iflag & IXON) || (cflag & CRTSCTS)) new_state |= TIOCM_DTR | TIOCM_RTS; else new_state &= ~(TIOCM_DTR | TIOCM_RTS); if (new_state != control_state) { gig_dbg(DEBUG_IF, "%u: new_state %x", cs->minor_index, new_state); gigaset_set_modem_ctrl(cs, control_state, new_state); control_state = new_state; } #endif /* save off the modified port settings */ cs->control_state = control_state; out: mutex_unlock(&cs->mutex); } /* wakeup tasklet for the write operation */ static void if_wake(unsigned long data) { struct cardstate *cs = (struct cardstate *) data; struct tty_struct *tty; tty = cs->tty; if (!tty) return; if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) { gig_dbg(DEBUG_IF, "write wakeup call"); tty->ldisc.write_wakeup(tty); } wake_up_interruptible(&tty->write_wait); } /*** interface to common ***/ void gigaset_if_init(struct cardstate *cs) { struct gigaset_driver *drv; drv = cs->driver; if (!drv->have_tty) return; tasklet_init(&cs->if_wake_tasklet, &if_wake, (unsigned long) cs); cs->tty_dev = tty_register_device(drv->tty, cs->minor_index, NULL); if (!IS_ERR(cs->tty_dev)) dev_set_drvdata(cs->tty_dev, cs); else { warn("could not register device to the tty subsystem"); cs->tty_dev = NULL; } } void gigaset_if_free(struct cardstate *cs) { struct gigaset_driver *drv; drv = cs->driver; if (!drv->have_tty) return; tasklet_disable(&cs->if_wake_tasklet); tasklet_kill(&cs->if_wake_tasklet); cs->tty_dev = NULL; tty_unregister_device(drv->tty, cs->minor_index); } void gigaset_if_receive(struct cardstate *cs, unsigned char *buffer, size_t len) { unsigned long flags; struct tty_struct *tty; spin_lock_irqsave(&cs->lock, flags); if ((tty = cs->tty) == NULL) gig_dbg(DEBUG_ANY, "receive on closed device"); else { tty_buffer_request_room(tty, len); tty_insert_flip_string(tty, buffer, len); tty_flip_buffer_push(tty); } spin_unlock_irqrestore(&cs->lock, flags); } EXPORT_SYMBOL_GPL(gigaset_if_receive); /* gigaset_if_initdriver * Initialize tty interface. * parameters: * drv Driver * procname Name of the driver (e.g. for /proc/tty/drivers) * devname Name of the device files (prefix without minor number) */ void gigaset_if_initdriver(struct gigaset_driver *drv, const char *procname, const char *devname) { unsigned minors = drv->minors; int ret; struct tty_driver *tty; drv->have_tty = 0; if ((drv->tty = alloc_tty_driver(minors)) == NULL) goto enomem; tty = drv->tty; tty->magic = TTY_DRIVER_MAGIC, tty->major = GIG_MAJOR, tty->type = TTY_DRIVER_TYPE_SERIAL, tty->subtype = SERIAL_TYPE_NORMAL, tty->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; tty->driver_name = procname; tty->name = devname; tty->minor_start = drv->minor; tty->num = drv->minors; tty->owner = THIS_MODULE; tty->init_termios = tty_std_termios; //FIXME tty->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; //FIXME tty_set_operations(tty, &if_ops); ret = tty_register_driver(tty); if (ret < 0) { warn("failed to register tty driver (error %d)", ret); goto error; } gig_dbg(DEBUG_IF, "tty driver initialized"); drv->have_tty = 1; return; enomem: warn("could not allocate tty structures"); error: if (drv->tty) put_tty_driver(drv->tty); } void gigaset_if_freedriver(struct gigaset_driver *drv) { if (!drv->have_tty) return; drv->have_tty = 0; tty_unregister_driver(drv->tty); put_tty_driver(drv->tty); }