diff options
Diffstat (limited to 'drivers/usb/class/cdc-acm.c')
| -rw-r--r-- | drivers/usb/class/cdc-acm.c | 209 | 
1 files changed, 175 insertions, 34 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 6d1f9b6aecf..69e859e0f51 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -106,6 +106,111 @@ static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int  	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); -	schedule_work(&acm->work); -out: -	acm->ready_for_write = 1; +	acm_write_done(acm); +	acm_write_start(acm); +	if (ACM_READY(acm)) +		schedule_work(&acm->work);  }  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"); +	memcpy(wb->buf, buf, count); +	wb->len = count; +	spin_unlock_irqrestore(&acm->write_lock, flags); -	acm->writeurb->transfer_buffer_length = count; -	acm->writeurb->dev = acm->dev; - -	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);  |