cdc-acm: add TIOCMIWAIT
authorOliver Neukum <oneukum@suse.de>
Wed, 20 Nov 2013 10:35:34 +0000 (11:35 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 3 Dec 2013 18:23:46 +0000 (10:23 -0800)
This implements TIOCMIWAIT for TIOCM_DSR, TIOCM_RI and TIOCM_CD
Disconnect is handled as TIOCM_CD or an error.

Signed-off-by: Oliver Neukum <oneukum@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/class/cdc-acm.c
drivers/usb/class/cdc-acm.h

index 3e7560f..7c00517 100644 (file)
@@ -262,6 +262,7 @@ static void acm_ctrl_irq(struct urb *urb)
        struct usb_cdc_notification *dr = urb->transfer_buffer;
        unsigned char *data;
        int newctrl;
+       int difference;
        int retval;
        int status = urb->status;
 
@@ -302,20 +303,31 @@ static void acm_ctrl_irq(struct urb *urb)
                        tty_port_tty_hangup(&acm->port, false);
                }
 
+               difference = acm->ctrlin ^ newctrl;
+               spin_lock(&acm->read_lock);
                acm->ctrlin = newctrl;
+               acm->oldcount = acm->iocount;
+
+               if (difference & ACM_CTRL_DSR)
+                       acm->iocount.dsr++;
+               if (difference & ACM_CTRL_BRK)
+                       acm->iocount.brk++;
+               if (difference & ACM_CTRL_RI)
+                       acm->iocount.rng++;
+               if (difference & ACM_CTRL_DCD)
+                       acm->iocount.dcd++;
+               if (difference & ACM_CTRL_FRAMING)
+                       acm->iocount.frame++;
+               if (difference & ACM_CTRL_PARITY)
+                       acm->iocount.parity++;
+               if (difference & ACM_CTRL_OVERRUN)
+                       acm->iocount.overrun++;
+               spin_unlock(&acm->read_lock);
+
+               if (difference)
+                       wake_up_all(&acm->wioctl);
 
-               dev_dbg(&acm->control->dev,
-                       "%s - input control lines: dcd%c dsr%c break%c "
-                       "ring%c framing%c parity%c overrun%c\n",
-                       __func__,
-                       acm->ctrlin & ACM_CTRL_DCD ? '+' : '-',
-                       acm->ctrlin & ACM_CTRL_DSR ? '+' : '-',
-                       acm->ctrlin & ACM_CTRL_BRK ? '+' : '-',
-                       acm->ctrlin & ACM_CTRL_RI  ? '+' : '-',
-                       acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-',
-                       acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-',
-                       acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-');
-                       break;
+               break;
 
        default:
                dev_dbg(&acm->control->dev,
@@ -796,6 +808,51 @@ static int set_serial_info(struct acm *acm,
        return retval;
 }
 
+static int wait_serial_change(struct acm *acm, unsigned long arg)
+{
+       int rv = 0;
+       DECLARE_WAITQUEUE(wait, current);
+       struct async_icount old, new;
+
+       if (arg & (TIOCM_DSR | TIOCM_RI | TIOCM_CD ))
+               return -EINVAL;
+       do {
+               spin_lock_irq(&acm->read_lock);
+               old = acm->oldcount;
+               new = acm->iocount;
+               acm->oldcount = new;
+               spin_unlock_irq(&acm->read_lock);
+
+               if ((arg & TIOCM_DSR) &&
+                       old.dsr != new.dsr)
+                       break;
+               if ((arg & TIOCM_CD)  &&
+                       old.dcd != new.dcd)
+                       break;
+               if ((arg & TIOCM_RI) &&
+                       old.rng != new.rng)
+                       break;
+
+               add_wait_queue(&acm->wioctl, &wait);
+               set_current_state(TASK_INTERRUPTIBLE);
+               schedule();
+               remove_wait_queue(&acm->wioctl, &wait);
+               if (acm->disconnected) {
+                       if (arg & TIOCM_CD)
+                               break;
+                       else
+                               rv = -ENODEV;
+               } else {
+                       if (signal_pending(current))
+                               rv = -ERESTARTSYS;
+               }
+       } while (!rv);
+
+       
+
+       return rv;
+}
+
 static int acm_tty_ioctl(struct tty_struct *tty,
                                        unsigned int cmd, unsigned long arg)
 {
@@ -809,6 +866,9 @@ static int acm_tty_ioctl(struct tty_struct *tty,
        case TIOCSSERIAL:
                rv = set_serial_info(acm, (struct serial_struct __user *) arg);
                break;
+       case TIOCMIWAIT:
+               rv = wait_serial_change(acm, arg);
+               break;
        }
 
        return rv;
@@ -1167,6 +1227,7 @@ made_compressed_probe:
        acm->readsize = readsize;
        acm->rx_buflimit = num_rx_buf;
        INIT_WORK(&acm->work, acm_softint);
+       init_waitqueue_head(&acm->wioctl);
        spin_lock_init(&acm->write_lock);
        spin_lock_init(&acm->read_lock);
        mutex_init(&acm->mutex);
@@ -1383,6 +1444,7 @@ static void acm_disconnect(struct usb_interface *intf)
                device_remove_file(&acm->control->dev,
                                &dev_attr_iCountryCodeRelDate);
        }
+       wake_up_all(&acm->wioctl);
        device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities);
        usb_set_intfdata(acm->control, NULL);
        usb_set_intfdata(acm->data, NULL);
index 0f76e4a..e38dc78 100644 (file)
@@ -106,6 +106,9 @@ struct acm {
        struct work_struct work;                        /* work queue entry for line discipline waking up */
        unsigned int ctrlin;                            /* input control lines (DCD, DSR, RI, break, overruns) */
        unsigned int ctrlout;                           /* output control lines (DTR, RTS) */
+       struct async_icount iocount;                    /* counters for control line changes */
+       struct async_icount oldcount;                   /* for comparison of counter */
+       wait_queue_head_t wioctl;                       /* for ioctl */
        unsigned int writesize;                         /* max packet size for the output bulk endpoint */
        unsigned int readsize,ctrlsize;                 /* buffer sizes for freeing */
        unsigned int minor;                             /* acm minor number */