Logo Search packages:      
Sourcecode: linux version File versions  Download package

interface.c

/*
 * interface to user space for the gigaset driver
 *
 * Copyright (c) 2004 by Hansjoerg Lipp <hjlipp@web.de>
 *
 * =====================================================================
 *    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 <linux/gigaset_dev.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>

/*** 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;

      if (cs->tty)
            tty_wakeup(cs->tty);
}

/*** 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);

      mutex_lock(&cs->mutex);
      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;
      }
      mutex_unlock(&cs->mutex);
}

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);
}

Generated by  Doxygen 1.6.0   Back to index