<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">From oliver@neukum.org Thu Apr 21 12:29:53 2005
From: Oliver Neukum &lt;oliver@neukum.org&gt;
To: greg@kroah.com
Subject: USB: fix acm trouble with terminals
Date: Thu, 21 Apr 2005 21:28:02 +0200


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: Pete Zaitcev &lt;zaitcev@redhat.com&gt;
Signed-off-by: Oliver Neukum &lt;oliver@neukum.name&gt;
Signed-off-by: Greg Kroah-Hartman &lt;gregkh@suse.de&gt;

diff -urp -X dontdiff linux-2.6.12-rc1/drivers/usb/class/cdc-acm.c linux-2.6.12-rc1-lem/drivers/usb/class/cdc-acm.c
--- linux-2.6.12-rc1/drivers/usb/class/cdc-acm.c	2005-03-18 17:12:12.000000000 -0800
+++ linux-2.6.12-rc1-lem/drivers/usb/class/cdc-acm.c	2005-04-18 13:53:26.000000000 -0700
@@ -106,6 +106,111 @@ static int acm_ctrl_msg(struct acm *acm,
 	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-&gt;write_current;
+	i = 0;
+	for (;;) {
+		wb = &amp;acm-&gt;wb[wbn];
+		if (!wb-&gt;use) {
+			wb-&gt;use = 1;
+			return wbn;
+		}
+		wbn = (wbn + 1) % ACM_NWB;
+		if (++i &gt;= ACM_NWB)
+			return -1;
+	}
+}
+
+static void acm_wb_free(struct acm *acm, int wbn)
+{
+	acm-&gt;wb[wbn].use = 0;
+}
+
+static int acm_wb_is_avail(struct acm *acm)
+{
+	int i, n;
+
+	n = 0;
+	for (i = 0; i &lt; ACM_NWB; i++) {
+		if (!acm-&gt;wb[i].use)
+			n++;
+	}
+	return n;
+}
+
+static inline int acm_wb_is_used(struct acm *acm, int wbn)
+{
+	return acm-&gt;wb[wbn].use;
+}
+
+/*
+ * Finish write.
+ */
+static void acm_write_done(struct acm *acm)
+{
+	unsigned long flags;
+	int wbn;
+
+	spin_lock_irqsave(&amp;acm-&gt;write_lock, flags);
+	acm-&gt;write_ready = 1;
+	wbn = acm-&gt;write_current;
+	acm_wb_free(acm, wbn);
+	acm-&gt;write_current = (wbn + 1) % ACM_NWB;
+	spin_unlock_irqrestore(&amp;acm-&gt;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(&amp;acm-&gt;write_lock, flags);
+	if (!acm-&gt;dev) {
+		spin_unlock_irqrestore(&amp;acm-&gt;write_lock, flags);
+		return -ENODEV;
+	}
+
+	if (!acm-&gt;write_ready) {
+		spin_unlock_irqrestore(&amp;acm-&gt;write_lock, flags);
+		return 0;	/* A white lie */
+	}
+
+	wbn = acm-&gt;write_current;
+	if (!acm_wb_is_used(acm, wbn)) {
+		spin_unlock_irqrestore(&amp;acm-&gt;write_lock, flags);
+		return 0;
+	}
+	wb = &amp;acm-&gt;wb[wbn];
+
+	acm-&gt;write_ready = 0;
+	spin_unlock_irqrestore(&amp;acm-&gt;write_lock, flags);
+
+	acm-&gt;writeurb-&gt;transfer_buffer = wb-&gt;buf;
+	acm-&gt;writeurb-&gt;transfer_dma = wb-&gt;dmah;
+	acm-&gt;writeurb-&gt;transfer_buffer_length = wb-&gt;len;
+	acm-&gt;writeurb-&gt;dev = acm-&gt;dev;
+
+	if ((rc = usb_submit_urb(acm-&gt;writeurb, GFP_ATOMIC)) &lt; 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
 static void acm_write_bulk(struct urb *urb, struct pt_regs *regs)
 {
 	struct acm *acm = (struct acm *)urb-&gt;context;
-	dbg("Entering acm_write_bulk with status %d\n", urb-&gt;status);
-
-	if (!ACM_READY(acm))
-		goto out;
 
-	if (urb-&gt;status)
-		dbg("nonzero write bulk status received: %d", urb-&gt;status);
+	dbg("Entering acm_write_bulk with status %d\n", urb-&gt;status);
 
-	schedule_work(&amp;acm-&gt;work);
-out:
-	acm-&gt;ready_for_write = 1;
+	acm_write_done(acm);
+	acm_write_start(acm);
+	if (ACM_READY(acm))
+		schedule_work(&amp;acm-&gt;work);
 }
 
 static void acm_softint(void *private)
@@ -351,32 +452,33 @@ static int acm_tty_write(struct tty_stru
 {
 	struct acm *acm = tty-&gt;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-&gt;ready_for_write)
-		return 0;
 	if (!count)
 		return 0;
 
-	count = (count &gt; acm-&gt;writesize) ? acm-&gt;writesize : count;
+	spin_lock_irqsave(&amp;acm-&gt;write_lock, flags);
+	if ((wbn = acm_wb_alloc(acm)) &lt; 0) {
+		spin_unlock_irqrestore(&amp;acm-&gt;write_lock, flags);
+		acm_write_start(acm);
+		return 0;
+	}
+	wb = &amp;acm-&gt;wb[wbn];
 
+	count = (count &gt; acm-&gt;writesize) ? acm-&gt;writesize : count;
 	dbg("Get %d bytes...", count);
-	memcpy(acm-&gt;write_buffer, buf, count);
-	dbg("  Successfully copied.\n");
+	memcpy(wb-&gt;buf, buf, count);
+	wb-&gt;len = count;
+	spin_unlock_irqrestore(&amp;acm-&gt;write_lock, flags);
 
-	acm-&gt;writeurb-&gt;transfer_buffer_length = count;
-	acm-&gt;writeurb-&gt;dev = acm-&gt;dev;
-
-	acm-&gt;ready_for_write = 0;
-	stat = usb_submit_urb(acm-&gt;writeurb, GFP_ATOMIC);
-	if (stat &lt; 0) {
-		dbg("usb_submit_urb(write bulk) failed");
-		acm-&gt;ready_for_write = 1;
+	if ((stat = acm_write_start(acm)) &lt; 0)
 		return stat;
-	}
-
 	return count;
 }
 
@@ -385,7 +487,11 @@ static int acm_tty_write_room(struct tty
 	struct acm *acm = tty-&gt;driver_data;
 	if (!ACM_READY(acm))
 		return -EINVAL;
-	return !acm-&gt;ready_for_write ? 0 : acm-&gt;writesize;
+	/*
+	 * Do not let the line discipline to know that we have a reserve,
+	 * or it might get too enthusiastic.
+	 */
+	return (acm-&gt;write_ready &amp;&amp; acm_wb_is_avail(acm)) ? acm-&gt;writesize : 0;
 }
 
 static int acm_tty_chars_in_buffer(struct tty_struct *tty)
@@ -393,7 +499,10 @@ static int acm_tty_chars_in_buffer(struc
 	struct acm *acm = tty-&gt;driver_data;
 	if (!ACM_READY(acm))
 		return -EINVAL;
-	return !acm-&gt;ready_for_write ? acm-&gt;writeurb-&gt;transfer_buffer_length : 0;
+	/*
+	 * This is inaccurate (overcounts), but it works.
+	 */
+	return (ACM_NWB - acm_wb_is_avail(acm)) * acm-&gt;writesize;
 }
 
 static void acm_tty_throttle(struct tty_struct *tty)
@@ -526,6 +635,39 @@ static void acm_tty_set_termios(struct t
  * 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 = &amp;acm-&gt;wb[0], i = 0; i &lt; ACM_NWB; i++, wb++) {
+		usb_buffer_free(acm-&gt;dev, acm-&gt;writesize, wb-&gt;buf, wb-&gt;dmah);
+	}
+}
+
+/* Little helper: write buffers allocate */
+static int acm_write_buffers_alloc(struct acm *acm)
+{
+	int i;
+	struct acm_wb *wb;
+
+	for (wb = &amp;acm-&gt;wb[0], i = 0; i &lt; ACM_NWB; i++, wb++) {
+		wb-&gt;buf = usb_buffer_alloc(acm-&gt;dev, acm-&gt;writesize, GFP_KERNEL,
+		    &amp;wb-&gt;dmah);
+		if (!wb-&gt;buf) {
+			while (i != 0) {
+				--i;
+				--wb;
+				usb_buffer_free(acm-&gt;dev, acm-&gt;writesize,
+				    wb-&gt;buf, wb-&gt;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-&gt;bh.data = (unsigned long) acm;
 	INIT_WORK(&amp;acm-&gt;work, acm_softint, acm);
 	spin_lock_init(&amp;acm-&gt;throttle_lock);
-	acm-&gt;ready_for_write = 1;
+	spin_lock_init(&amp;acm-&gt;write_lock);
+	acm-&gt;write_ready = 1;
 
 	buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &amp;acm-&gt;ctrl_dma);
 	if (!buf) {
@@ -716,12 +859,10 @@ skip_normal_probe:
 	}
 	acm-&gt;read_buffer = buf;
 
-	buf = usb_buffer_alloc(usb_dev, acm-&gt;writesize, GFP_KERNEL, &amp;acm-&gt;write_dma);
-	if (!buf) {
+	if (acm_write_buffers_alloc(acm) &lt; 0) {
 		dev_dbg(&amp;intf-&gt;dev, "out of memory (write buffer alloc)\n");
 		goto alloc_fail4;
 	}
-	acm-&gt;write_buffer = buf;	
 
 	acm-&gt;ctrlurb = usb_alloc_urb(0, GFP_KERNEL);
 	if (!acm-&gt;ctrlurb) {
@@ -750,9 +891,9 @@ skip_normal_probe:
 	acm-&gt;readurb-&gt;transfer_dma = acm-&gt;read_dma;
 
 	usb_fill_bulk_urb(acm-&gt;writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite-&gt;bEndpointAddress),
-			  acm-&gt;write_buffer, acm-&gt;writesize, acm_write_bulk, acm);
+			  NULL, acm-&gt;writesize, acm_write_bulk, acm);
 	acm-&gt;writeurb-&gt;transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP;
-	acm-&gt;writeurb-&gt;transfer_dma = acm-&gt;write_dma;
+	/* acm-&gt;writeurb-&gt;transfer_dma = 0; */
 
 	dev_info(&amp;intf-&gt;dev, "ttyACM%d: USB ACM device\n", minor);
 
@@ -775,7 +916,7 @@ alloc_fail7:
 alloc_fail6:
 	usb_free_urb(acm-&gt;ctrlurb);
 alloc_fail5:
-	usb_buffer_free(usb_dev, acm-&gt;writesize, acm-&gt;write_buffer, acm-&gt;write_dma);
+	acm_write_buffers_free(acm);
 alloc_fail4:
 	usb_buffer_free(usb_dev, readsize, acm-&gt;read_buffer, acm-&gt;read_dma);
 alloc_fail3:
@@ -806,7 +947,7 @@ static void acm_disconnect(struct usb_in
 
 	flush_scheduled_work(); /* wait for acm_softint */
 
-	usb_buffer_free(usb_dev, acm-&gt;writesize, acm-&gt;write_buffer, acm-&gt;write_dma);
+	acm_write_buffers_free(acm);
 	usb_buffer_free(usb_dev, acm-&gt;readsize, acm-&gt;read_buffer, acm-&gt;read_dma);
 	usb_buffer_free(usb_dev, acm-&gt;ctrlsize, acm-&gt;ctrl_buffer, acm-&gt;ctrl_dma);
 
diff -urp -X dontdiff linux-2.6.12-rc1/drivers/usb/class/cdc-acm.h linux-2.6.12-rc1-lem/drivers/usb/class/cdc-acm.h
--- linux-2.6.12-rc1/drivers/usb/class/cdc-acm.h	2005-03-18 17:12:12.000000000 -0800
+++ linux-2.6.12-rc1-lem/drivers/usb/class/cdc-acm.h	2005-03-19 17:09:44.000000000 -0800
@@ -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 */
 };

Signed-off-by: Pete Zaitcev &lt;zaitcev@redhat.com&gt;

-- Pete


</pre></body></html>