From 5a6a62bdb9257aa74ab0ad2b2c8a33b0f9b17ce4 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 20 Nov 2013 11:35:34 +0100 Subject: [PATCH] cdc-acm: add TIOCMIWAIT 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 Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 86 ++++++++++++++++++++++++++++++++++++++------- drivers/usb/class/cdc-acm.h | 3 ++ 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 3e7560f..7c00517 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -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); diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 0f76e4a..e38dc78 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -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 */ -- 2.7.4