diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index fa4e23930614..1d2c99af2532 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -713,9 +713,20 @@ static int acm_tty_write(struct tty_struct *tty, } if (acm->susp_count) { + if (acm->putbuffer) { + /* now to preserve order */ + usb_anchor_urb(acm->putbuffer->urb, &acm->delayed); + acm->putbuffer = NULL; + } usb_anchor_urb(wb->urb, &acm->delayed); spin_unlock_irqrestore(&acm->write_lock, flags); return count; + } else { + if (acm->putbuffer) { + /* at this point there is no good way to handle errors */ + acm_start_wb(acm, acm->putbuffer); + acm->putbuffer = NULL; + } } stat = acm_start_wb(acm, wb); @@ -726,6 +737,60 @@ static int acm_tty_write(struct tty_struct *tty, return count; } +static void acm_tty_flush_chars(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + struct acm_wb *cur = acm->putbuffer; + int err; + unsigned long flags; + + acm->putbuffer = NULL; + err = usb_autopm_get_interface_async(acm->control); + spin_lock_irqsave(&acm->write_lock, flags); + if (err < 0) { + cur->use = 0; + goto out; + } + + if (acm->susp_count) + usb_anchor_urb(cur->urb, &acm->delayed); + else + acm_start_wb(acm, cur); +out: + spin_unlock_irqrestore(&acm->write_lock, flags); + return; +} + +static int acm_tty_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct acm *acm = tty->driver_data; + struct acm_wb *cur; + int wbn; + unsigned long flags; + +overflow: + cur = acm->putbuffer; + if (!cur) { + spin_lock_irqsave(&acm->write_lock, flags); + wbn = acm_wb_alloc(acm); + if (wbn >= 0) { + cur = &acm->wb[wbn]; + acm->putbuffer = cur; + } + spin_unlock_irqrestore(&acm->write_lock, flags); + if (!cur) + return 0; + } + + if (cur->len == acm->writesize) { + acm_tty_flush_chars(tty); + goto overflow; + } + + cur->buf[cur->len++] = ch; + return 1; +} + static int acm_tty_write_room(struct tty_struct *tty) { struct acm *acm = tty->driver_data; @@ -1905,6 +1970,8 @@ static const struct tty_operations acm_ops = { .cleanup = acm_tty_cleanup, .hangup = acm_tty_hangup, .write = acm_tty_write, + .put_char = acm_tty_put_char, + .flush_chars = acm_tty_flush_chars, .write_room = acm_tty_write_room, .ioctl = acm_tty_ioctl, .throttle = acm_tty_throttle, diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index ccfaba9ab4e4..05ce308d5d2a 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -94,6 +94,7 @@ struct acm { unsigned long read_urbs_free; struct urb *read_urbs[ACM_NR]; struct acm_rb read_buffers[ACM_NR]; + struct acm_wb *putbuffer; /* for acm_tty_put_char() */ int rx_buflimit; int rx_endpoint; spinlock_t read_lock;