Commit 884b600f authored by Oliver Neukum's avatar Oliver Neukum Committed by Greg Kroah-Hartman

[PATCH] USB: fix acm trouble with terminals

This patch fixes lost LF when ACM device is used with getty/login/bash,
in case of a modem which takes calls.
Signed-off-by: default avatarPete Zaitcev <zaitcev@redhat.com>
Signed-off-by: default avatarOliver Neukum <oliver@neukum.name>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d5926ae7
......@@ -105,6 +105,111 @@ static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int
#define acm_send_break(acm, ms) \
acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0)
/*
* Write buffer management.
* All of these assume proper locks taken by the caller.
*/
static int acm_wb_alloc(struct acm *acm)
{
int i, wbn;
struct acm_wb *wb;
wbn = acm->write_current;
i = 0;
for (;;) {
wb = &acm->wb[wbn];
if (!wb->use) {
wb->use = 1;
return wbn;
}
wbn = (wbn + 1) % ACM_NWB;
if (++i >= ACM_NWB)
return -1;
}
}
static void acm_wb_free(struct acm *acm, int wbn)
{
acm->wb[wbn].use = 0;
}
static int acm_wb_is_avail(struct acm *acm)
{
int i, n;
n = 0;
for (i = 0; i < ACM_NWB; i++) {
if (!acm->wb[i].use)
n++;
}
return n;
}
static inline int acm_wb_is_used(struct acm *acm, int wbn)
{
return acm->wb[wbn].use;
}
/*
* Finish write.
*/
static void acm_write_done(struct acm *acm)
{
unsigned long flags;
int wbn;
spin_lock_irqsave(&acm->write_lock, flags);
acm->write_ready = 1;
wbn = acm->write_current;
acm_wb_free(acm, wbn);
acm->write_current = (wbn + 1) % ACM_NWB;
spin_unlock_irqrestore(&acm->write_lock, flags);
}
/*
* Poke write.
*/
static int acm_write_start(struct acm *acm)
{
unsigned long flags;
int wbn;
struct acm_wb *wb;
int rc;
spin_lock_irqsave(&acm->write_lock, flags);
if (!acm->dev) {
spin_unlock_irqrestore(&acm->write_lock, flags);
return -ENODEV;
}
if (!acm->write_ready) {
spin_unlock_irqrestore(&acm->write_lock, flags);
return 0; /* A white lie */
}
wbn = acm->write_current;
if (!acm_wb_is_used(acm, wbn)) {
spin_unlock_irqrestore(&acm->write_lock, flags);
return 0;
}
wb = &acm->wb[wbn];
acm->write_ready = 0;
spin_unlock_irqrestore(&acm->write_lock, flags);
acm->writeurb->transfer_buffer = wb->buf;
acm->writeurb->transfer_dma = wb->dmah;
acm->writeurb->transfer_buffer_length = wb->len;
acm->writeurb->dev = acm->dev;
if ((rc = usb_submit_urb(acm->writeurb, GFP_ATOMIC)) < 0) {
dbg("usb_submit_urb(write bulk) failed: %d", rc);
acm_write_done(acm);
}
return rc;
}
/*
* Interrupt handlers for various ACM device responses
*/
......@@ -237,17 +342,13 @@ static void acm_rx_tasklet(unsigned long _acm)
static void acm_write_bulk(struct urb *urb, struct pt_regs *regs)
{
struct acm *acm = (struct acm *)urb->context;
dbg("Entering acm_write_bulk with status %d\n", urb->status);
if (!ACM_READY(acm))
goto out;
if (urb->status)
dbg("nonzero write bulk status received: %d", urb->status);
dbg("Entering acm_write_bulk with status %d\n", urb->status);
acm_write_done(acm);
acm_write_start(acm);
if (ACM_READY(acm))
schedule_work(&acm->work);
out:
acm->ready_for_write = 1;
}
static void acm_softint(void *private)
......@@ -351,32 +452,33 @@ static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int c
{
struct acm *acm = tty->driver_data;
int stat;
unsigned long flags;
int wbn;
struct acm_wb *wb;
dbg("Entering acm_tty_write to write %d bytes,\n", count);
if (!ACM_READY(acm))
return -EINVAL;
if (!acm->ready_for_write)
return 0;
if (!count)
return 0;
count = (count > acm->writesize) ? acm->writesize : count;
spin_lock_irqsave(&acm->write_lock, flags);
if ((wbn = acm_wb_alloc(acm)) < 0) {
spin_unlock_irqrestore(&acm->write_lock, flags);
acm_write_start(acm);
return 0;
}
wb = &acm->wb[wbn];
count = (count > acm->writesize) ? acm->writesize : count;
dbg("Get %d bytes...", count);
memcpy(acm->write_buffer, buf, count);
dbg(" Successfully copied.\n");
acm->writeurb->transfer_buffer_length = count;
acm->writeurb->dev = acm->dev;
memcpy(wb->buf, buf, count);
wb->len = count;
spin_unlock_irqrestore(&acm->write_lock, flags);
acm->ready_for_write = 0;
stat = usb_submit_urb(acm->writeurb, GFP_ATOMIC);
if (stat < 0) {
dbg("usb_submit_urb(write bulk) failed");
acm->ready_for_write = 1;
if ((stat = acm_write_start(acm)) < 0)
return stat;
}
return count;
}
......@@ -385,7 +487,11 @@ static int acm_tty_write_room(struct tty_struct *tty)
struct acm *acm = tty->driver_data;
if (!ACM_READY(acm))
return -EINVAL;
return !acm->ready_for_write ? 0 : acm->writesize;
/*
* Do not let the line discipline to know that we have a reserve,
* or it might get too enthusiastic.
*/
return (acm->write_ready && acm_wb_is_avail(acm)) ? acm->writesize : 0;
}
static int acm_tty_chars_in_buffer(struct tty_struct *tty)
......@@ -393,7 +499,10 @@ static int acm_tty_chars_in_buffer(struct tty_struct *tty)
struct acm *acm = tty->driver_data;
if (!ACM_READY(acm))
return -EINVAL;
return !acm->ready_for_write ? acm->writeurb->transfer_buffer_length : 0;
/*
* This is inaccurate (overcounts), but it works.
*/
return (ACM_NWB - acm_wb_is_avail(acm)) * acm->writesize;
}
static void acm_tty_throttle(struct tty_struct *tty)
......@@ -526,6 +635,39 @@ static void acm_tty_set_termios(struct tty_struct *tty, struct termios *termios_
* USB probe and disconnect routines.
*/
/* Little helper: write buffers free */
static void acm_write_buffers_free(struct acm *acm)
{
int i;
struct acm_wb *wb;
for (wb = &acm->wb[0], i = 0; i < ACM_NWB; i++, wb++) {
usb_buffer_free(acm->dev, acm->writesize, wb->buf, wb->dmah);
}
}
/* Little helper: write buffers allocate */
static int acm_write_buffers_alloc(struct acm *acm)
{
int i;
struct acm_wb *wb;
for (wb = &acm->wb[0], i = 0; i < ACM_NWB; i++, wb++) {
wb->buf = usb_buffer_alloc(acm->dev, acm->writesize, GFP_KERNEL,
&wb->dmah);
if (!wb->buf) {
while (i != 0) {
--i;
--wb;
usb_buffer_free(acm->dev, acm->writesize,
wb->buf, wb->dmah);
}
return -ENOMEM;
}
}
return 0;
}
static int acm_probe (struct usb_interface *intf,
const struct usb_device_id *id)
{
......@@ -700,7 +842,8 @@ skip_normal_probe:
acm->bh.data = (unsigned long) acm;
INIT_WORK(&acm->work, acm_softint, acm);
spin_lock_init(&acm->throttle_lock);
acm->ready_for_write = 1;
spin_lock_init(&acm->write_lock);
acm->write_ready = 1;
buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
if (!buf) {
......@@ -716,12 +859,10 @@ skip_normal_probe:
}
acm->read_buffer = buf;
buf = usb_buffer_alloc(usb_dev, acm->writesize, GFP_KERNEL, &acm->write_dma);
if (!buf) {
if (acm_write_buffers_alloc(acm) < 0) {
dev_dbg(&intf->dev, "out of memory (write buffer alloc)\n");
goto alloc_fail4;
}
acm->write_buffer = buf;
acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
if (!acm->ctrlurb) {
......@@ -750,9 +891,9 @@ skip_normal_probe:
acm->readurb->transfer_dma = acm->read_dma;
usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress),
acm->write_buffer, acm->writesize, acm_write_bulk, acm);
NULL, acm->writesize, acm_write_bulk, acm);
acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP;
acm->writeurb->transfer_dma = acm->write_dma;
/* acm->writeurb->transfer_dma = 0; */
dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor);
......@@ -775,7 +916,7 @@ alloc_fail7:
alloc_fail6:
usb_free_urb(acm->ctrlurb);
alloc_fail5:
usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma);
acm_write_buffers_free(acm);
alloc_fail4:
usb_buffer_free(usb_dev, readsize, acm->read_buffer, acm->read_dma);
alloc_fail3:
......@@ -806,7 +947,7 @@ static void acm_disconnect(struct usb_interface *intf)
flush_scheduled_work(); /* wait for acm_softint */
usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma);
acm_write_buffers_free(acm);
usb_buffer_free(usb_dev, acm->readsize, acm->read_buffer, acm->read_dma);
usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
......
......@@ -51,14 +51,34 @@
* Internal driver structures.
*/
/*
* The only reason to have several buffers is to accomodate assumptions
* in line disciplines. They ask for empty space amount, receive our URB size,
* and proceed to issue several 1-character writes, assuming they will fit.
* The very first write takes a complete URB. Fortunately, this only happens
* when processing onlcr, so we only need 2 buffers.
*/
#define ACM_NWB 2
struct acm_wb {
unsigned char *buf;
dma_addr_t dmah;
int len;
int use;
};
struct acm {
struct usb_device *dev; /* the corresponding usb device */
struct usb_interface *control; /* control interface */
struct usb_interface *data; /* data interface */
struct tty_struct *tty; /* the corresponding tty */
struct urb *ctrlurb, *readurb, *writeurb; /* urbs */
u8 *ctrl_buffer, *read_buffer, *write_buffer; /* buffers of urbs */
dma_addr_t ctrl_dma, read_dma, write_dma; /* dma handles of buffers */
u8 *ctrl_buffer, *read_buffer; /* buffers of urbs */
dma_addr_t ctrl_dma, read_dma; /* dma handles of buffers */
struct acm_wb wb[ACM_NWB];
int write_current; /* current write buffer */
int write_used; /* number of non-empty write buffers */
int write_ready; /* write urb is not running */
spinlock_t write_lock;
struct usb_cdc_line_coding line; /* bits, stop, parity */
struct work_struct work; /* work queue entry for line discipline waking up */
struct tasklet_struct bh; /* rx processing */
......@@ -71,7 +91,6 @@ struct acm {
unsigned int minor; /* acm minor number */
unsigned char throttle; /* throttled by tty layer */
unsigned char clocal; /* termios CLOCAL */
unsigned char ready_for_write; /* write urb can be used */
unsigned char resubmit_to_unthrottle; /* throtteling has disabled the read urb */
unsigned int ctrl_caps; /* control capabilities from the class specific header */
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment