Update modulu pro CDC ACM modemy

David Kubicek dave na dave.ok.cz
Středa Červen 8 18:45:24 CEST 2005


Dobry den vsem,

zasilam oznameni o dokonceni update modulu cdc-acm, ktery se mimo jine
pouziva pro pripojeni CDMA pro ET. Byl odstranen rychlostni limit a
vylepsena stabilita i s ohledem na USB charakter zarizeni (vytrzeni
kabelu za chodu, apod.) Vice viz novinky root.cz nebo tamni odkaz na muj
server, kde si kazdy muze stahnout patche na par aktualnich jader.

Prikladam patch pro soucasnou 2.6 stable verzi a jeji MD5.

linux-2.6.11.11_stable-cdc-acm.patch
:: 49ca21b9dc54708f6b0014fb9ba521d8

-- 
David Kubicek
system specialist

gedas CR s.r.o.
TGM 840, Mlada Boleslav
Czech Republic
------------- další část ---------------
--- linux-2.6.11.11/drivers/usb/class/cdc-acm.c	2005-05-27 07:06:46.000000000 +0200
+++ linux-2.6.12-rc5-dave/drivers/usb/class/cdc-acm.c	2005-06-07 14:38:14.000000000 +0200
@@ -6,6 +6,7 @@
  * Copyright (c) 1999 Johannes Erdfelt	<johannes na erdfelt.com>
  * Copyright (c) 2000 Vojtech Pavlik	<vojtech na suse.cz>
  * Copyright (c) 2004 Oliver Neukum	<oliver na neukum.name>
+ * Copyright (c) 2005 David Kubicek	<dave na dave.ok.cz>
  *
  * USB Abstract Control Model driver for USB modems and ISDN adapters
  *
@@ -29,6 +30,13 @@
  *		config we want, sysadmin changes bConfigurationValue in sysfs.
  *	v0.23 - use softirq for rx processing, as needed by tty layer
  *	v0.24 - change probe method to evaluate CDC union descriptor
+ *	v0.25 - 1) downstream tasks paralelized to maximize throughput (max was
+ *	           cca 32 KiB/s (a bit more using wMaxPacketSize resize hack))
+ *	        2) fixed known kernel fault when USB disconnected during
+ *	           established connection (attached device opened and r/w upon)
+ *	        3) module now loads & unloads cleanly even if cable was peviously
+ *	           unplugged during established connection or a similar 
+ *	           unexpected event occured
  */
 
 /*
@@ -60,21 +68,24 @@
 #include <linux/smp_lock.h>
 #include <asm/uaccess.h>
 #include <linux/usb.h>
+#include <linux/usb_cdc.h>
 #include <asm/byteorder.h>
 #include <asm/unaligned.h>
+#include <linux/list.h>
 
 #include "cdc-acm.h"
 
 /*
  * Version Information
  */
-#define DRIVER_VERSION "v0.23"
-#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik"
+#define DRIVER_VERSION "v0.25"
+#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek"
 #define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"
 
 static struct usb_driver acm_driver;
 static struct tty_driver *acm_tty_driver;
 static struct acm *acm_table[ACM_TTY_MINORS];
+static int acm_disconnected = 0;
 
 static DECLARE_MUTEX(open_sem);
 
@@ -89,7 +100,7 @@
 	int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0),
 		request, USB_RT_ACM, value,
 		acm->control->altsetting[0].desc.bInterfaceNumber,
-		buf, len, HZ * 5);
+		buf, len, 5000);
 	dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval);
 	return retval < 0 ? retval : 0;
 }
@@ -97,9 +108,12 @@
 /* devices aren't required to support these requests.
  * the cdc acm descriptor tells whether they do...
  */
-#define acm_set_control(acm, control)	acm_ctrl_msg(acm, ACM_REQ_SET_CONTROL, control, NULL, 0)
-#define acm_set_line(acm, line)		acm_ctrl_msg(acm, ACM_REQ_SET_LINE, 0, line, sizeof(struct acm_line))
-#define acm_send_break(acm, ms)		acm_ctrl_msg(acm, ACM_REQ_SEND_BREAK, ms, NULL, 0)
+#define acm_set_control(acm, control) \
+	acm_ctrl_msg(acm, USB_CDC_REQ_SET_CONTROL_LINE_STATE, control, NULL, 0)
+#define acm_set_line(acm, line) \
+	acm_ctrl_msg(acm, USB_CDC_REQ_SET_LINE_CODING, 0, line, sizeof *(line))
+#define acm_send_break(acm, ms) \
+	acm_ctrl_msg(acm, USB_CDC_REQ_SEND_BREAK, ms, NULL, 0)
 
 /*
  * Interrupt handlers for various ACM device responses
@@ -109,7 +123,7 @@
 static void acm_ctrl_irq(struct urb *urb, struct pt_regs *regs)
 {
 	struct acm *acm = urb->context;
-	struct usb_ctrlrequest *dr = urb->transfer_buffer;
+	struct usb_cdc_notification *dr = urb->transfer_buffer;
 	unsigned char *data;
 	int newctrl;
 	int status;
@@ -133,14 +147,14 @@
 		goto exit;
 
 	data = (unsigned char *)(dr + 1);
-	switch (dr->bRequest) {
+	switch (dr->bNotificationType) {
 
-		case ACM_IRQ_NETWORK:
+		case USB_CDC_NOTIFY_NETWORK_CONNECTION:
 
 			dbg("%s network", dr->wValue ? "connected to" : "disconnected from");
 			break;
 
-		case ACM_IRQ_LINE_STATE:
+		case USB_CDC_NOTIFY_SERIAL_STATE:
 
 			newctrl = le16_to_cpu(get_unaligned((__le16 *) data));
 
@@ -160,8 +174,9 @@
 			break;
 
 		default:
-			dbg("unknown control event received: request %d index %d len %d data0 %d data1 %d",
-				dr->bRequest, dr->wIndex, dr->wLength, data[0], data[1]);
+			dbg("unknown notification %d received: index %d len %d data0 %d data1 %d",
+				dr->bNotificationType, dr->wIndex,
+				dr->wLength, data[0], data[1]);
 			break;
 	}
 exit:
@@ -174,58 +189,126 @@
 /* data interface returns incoming bytes, or we got unthrottled */
 static void acm_read_bulk(struct urb *urb, struct pt_regs *regs)
 {
-	struct acm *acm = urb->context;
-	dbg("Entering acm_read_bulk with status %d\n", urb->status);
+	struct acm *acm = NULL;
+	struct acm_read_buffer *buf;
+	struct acm_read_urb *rcv = NULL;
+	unsigned long flags;
+
+	if (urb && (rcv = urb->context))
+		acm = rcv->instance;
 
 	if (!ACM_READY(acm))
 		return;
 
-	if (urb->status)
-		dev_dbg(&acm->data->dev, "bulk rx status %d\n", urb->status);
+	if (!rcv || urb <= 0) {
+		spin_lock_irqsave(&rcv->instance->read_lock, flags);
+		list_add_tail(&rcv->list, &rcv->instance->spare_read_urbs);
+		list_add_tail(&rcv->buffer->list, &rcv->instance->filled_read_bufs);
+		spin_unlock_irqrestore(&rcv->instance->read_lock, flags);
+		return;
+	}
+
+	dbg("acm_read_bulk: urb = 0x%p\n", urb);
 
-	/* calling tty_flip_buffer_push() in_irq() isn't allowed */
-	tasklet_schedule(&acm->bh);
+	buf = rcv->buffer;
+	buf->size = urb->actual_length;
+
+	spin_lock_irqsave(&acm->read_lock, flags);
+	list_add_tail(&rcv->list, &acm->spare_read_urbs);
+	list_add_tail(&buf->list, &acm->filled_read_bufs);
+	spin_unlock_irqrestore(&acm->read_lock, flags);
+
+	tasklet_schedule(&acm->urb_task);
 }
 
 static void acm_rx_tasklet(unsigned long _acm)
 {
+	struct acm_read_buffer *buf;
 	struct acm *acm = (void *)_acm;
-	struct urb *urb = acm->readurb;
 	struct tty_struct *tty = acm->tty;
-	unsigned char *data = urb->transfer_buffer;
+	struct acm_read_urb *rcv;
+	unsigned long flags;
+	int err;
 	int i = 0;
-	dbg("Entering acm_rx_tasklet");
 
-	if (urb->actual_length > 0 && !acm->throttle)  {
-		for (i = 0; i < urb->actual_length && !acm->throttle; i++) {
-			/* if we insert more than TTY_FLIPBUF_SIZE characters,
-			 * we drop them. */
-			if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
-				tty_flip_buffer_push(tty);
-			}
-			tty_insert_flip_char(tty, data[i], 0);
+	if (!ACM_READY(acm) || acm->throttle)
+		return;
+
+next_buffer:
+	spin_lock_irq(&acm->read_lock);
+	if (list_empty(&acm->filled_read_bufs)) {
+		spin_unlock_irq(&acm->read_lock);
+		goto urbs;
+	}
+	buf = list_entry(acm->filled_read_bufs.next,
+			 struct acm_read_buffer, list);
+	list_del(&buf->list);
+	spin_unlock_irq(&acm->read_lock);
+
+	dbg("acm_rx_tasklet: procesing buf 0x%p, size = %d\n", buf, buf->size);
+
+	for (i = 0; i < buf->size && !acm->throttle; i++) {
+		/* if we insert more than TTY_FLIPBUF_SIZE characters,
+		 * we drop them. */
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+			tty_flip_buffer_push(tty);
 		}
-		dbg("Handed %d bytes to tty layer", i+1);
-		tty_flip_buffer_push(tty);
+		tty_insert_flip_char(tty, buf->base[i], 0);
 	}
+	tty_flip_buffer_push(tty);
 
 	spin_lock(&acm->throttle_lock);
 	if (acm->throttle) {
 		dbg("Throtteling noticed");
-		memmove(data, data + i, urb->actual_length - i);
-		urb->actual_length -= i;
-		acm->resubmit_to_unthrottle = 1;
+		memmove(buf->base, buf->base + i, buf->size - i);
+		buf->size -= i;
 		spin_unlock(&acm->throttle_lock);
+		spin_lock_irq(&acm->read_lock);
+		list_add(&buf->list, &acm->filled_read_bufs);
+		spin_unlock_irq(&acm->read_lock);
 		return;
 	}
 	spin_unlock(&acm->throttle_lock);
 
-	urb->actual_length = 0;
-	urb->dev = acm->dev;
-
-	i = usb_submit_urb(urb, GFP_ATOMIC);
-	if (i)
-		dev_dbg(&acm->data->dev, "bulk rx resubmit %d\n", i);
+	spin_lock_irqsave(&acm->read_lock, flags);
+	list_add(&buf->list, &acm->spare_read_bufs);
+	spin_unlock_irqrestore(&acm->read_lock, flags);
+	goto next_buffer;
+
+urbs:
+	while (!list_empty(&acm->spare_read_bufs)) {
+		spin_lock_irq(&acm->read_lock);
+		if (list_empty(&acm->spare_read_urbs)) {
+			spin_unlock_irq(&acm->read_lock);
+			return;
+		}
+		rcv = list_entry(acm->spare_read_urbs.next,
+				 struct acm_read_urb, list);
+		list_del(&rcv->list);
+		spin_unlock_irq(&acm->read_lock);
+
+		buf = list_entry(acm->spare_read_bufs.next,
+				 struct acm_read_buffer, list);
+		list_del(&buf->list);
+
+		rcv->buffer = buf;
+
+		usb_fill_bulk_urb(rcv->urb, acm->dev,
+				  acm->rx_endpoint,
+				  buf->base,
+				  acm->readsize,
+				  acm_read_bulk, rcv);
+
+		dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p\n", rcv->urb, rcv, buf);
+
+		if ((err = usb_submit_urb(rcv->urb, GFP_ATOMIC)) < 0) {
+			list_add(&buf->list, &acm->spare_read_bufs);
+			spin_lock_irq(&acm->read_lock);
+			list_add(&rcv->list, &acm->spare_read_urbs);
+			spin_unlock_irq(&acm->read_lock);
+			return;
+		}
+	}
 }
 
 /* data interface wrote those outgoing bytes */
@@ -263,6 +346,7 @@
 {
 	struct acm *acm;
 	int rv = -EINVAL;
+	int i;
 	dbg("Entering acm_tty_open.\n");
 	
 	down(&open_sem);
@@ -276,7 +360,9 @@
 	tty->driver_data = acm;
 	acm->tty = tty;
 
-
+	/* force low_latency on so that our tty_push actually forces the data through, 
+	   otherwise it is scheduled, and with high data rates data can get lost. */
+	tty->low_latency = 1;
 
 	if (acm->used++) {
 		goto done;
@@ -288,18 +374,20 @@
 		goto bail_out;
 	}
 
-	acm->readurb->dev = acm->dev;
-	if (usb_submit_urb(acm->readurb, GFP_KERNEL)) {
-		dbg("usb_submit_urb(read bulk) failed");
-		goto bail_out_and_unlink;
-	}
-
 	if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS))
 		goto full_bailout;
 
-	/* force low_latency on so that our tty_push actually forces the data through, 
-	   otherwise it is scheduled, and with high data rates data can get lost. */
-	tty->low_latency = 1;
+	INIT_LIST_HEAD(&acm->spare_read_urbs);
+	INIT_LIST_HEAD(&acm->spare_read_bufs);
+	INIT_LIST_HEAD(&acm->filled_read_bufs);
+	for (i = 0; i < ACM_MAX_READ_URBS; i++) {
+		list_add(&(acm->read_urbs[i].list), &acm->spare_read_urbs);
+	}
+	for (i = 0; i < ACM_MAX_READ_BUFS; i++) {
+		list_add(&(acm->read_bufs[i].list), &acm->spare_read_bufs);
+	}
+
+	tasklet_schedule(&acm->urb_task);
 
 done:
 err_out:
@@ -307,8 +395,6 @@
 	return rv;
 
 full_bailout:
-	usb_kill_urb(acm->readurb);
-bail_out_and_unlink:
 	usb_kill_urb(acm->ctrlurb);
 bail_out:
 	acm->used--;
@@ -319,6 +405,7 @@
 static void acm_tty_close(struct tty_struct *tty, struct file *filp)
 {
 	struct acm *acm = tty->driver_data;
+	int i;
 
 	if (!acm || !acm->used)
 		return;
@@ -329,12 +416,13 @@
 			acm_set_control(acm, acm->ctrlout = 0);
 			usb_kill_urb(acm->ctrlurb);
 			usb_kill_urb(acm->writeurb);
-			usb_kill_urb(acm->readurb);
+			for (i = 0; i < ACM_MAX_READ_URBS; i++)
+				usb_kill_urb(acm->read_urbs[i].urb);
 		} else {
-			tty_unregister_device(acm_tty_driver, acm->minor);
 			acm_table[acm->minor] = NULL;
 			usb_free_urb(acm->ctrlurb);
-			usb_free_urb(acm->readurb);
+			for (i = 0; i < ACM_MAX_READ_URBS; i++)
+				usb_free_urb(acm->read_urbs[i].urb);
 			usb_free_urb(acm->writeurb);
 			kfree(acm);
 		}
@@ -409,10 +497,7 @@
 	spin_lock_bh(&acm->throttle_lock);
 	acm->throttle = 0;
 	spin_unlock_bh(&acm->throttle_lock);
-	if (acm->resubmit_to_unthrottle) {
-		acm->resubmit_to_unthrottle = 0;
-		acm_read_bulk(acm->readurb, NULL);
-	}
+	tasklet_schedule(&acm->urb_task);
 }
 
 static void acm_tty_break_ctl(struct tty_struct *tty, int state)
@@ -485,32 +570,34 @@
 {
 	struct acm *acm = tty->driver_data;
 	struct termios *termios = tty->termios;
-	struct acm_line newline;
+	struct usb_cdc_line_coding newline;
 	int newctrl = acm->ctrlout;
 
 	if (!ACM_READY(acm))
 		return;
 
-	newline.speed = cpu_to_le32p(acm_tty_speed +
+	newline.dwDTERate = cpu_to_le32p(acm_tty_speed +
 		(termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0));
-	newline.stopbits = termios->c_cflag & CSTOPB ? 2 : 0;
-	newline.parity = termios->c_cflag & PARENB ?
+	newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 0;
+	newline.bParityType = termios->c_cflag & PARENB ?
 		(termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0;
-	newline.databits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];
+	newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4];
 
 	acm->clocal = ((termios->c_cflag & CLOCAL) != 0);
 
-	if (!newline.speed) {
-		newline.speed = acm->line.speed;
+	if (!newline.dwDTERate) {
+		newline.dwDTERate = acm->line.dwDTERate;
 		newctrl &= ~ACM_CTRL_DTR;
 	} else  newctrl |=  ACM_CTRL_DTR;
 
 	if (newctrl != acm->ctrlout)
 		acm_set_control(acm, acm->ctrlout = newctrl);
 
-	if (memcmp(&acm->line, &newline, sizeof(struct acm_line))) {
-		memcpy(&acm->line, &newline, sizeof(struct acm_line));
-		dbg("set line: %d %d %d %d", newline.speed, newline.stopbits, newline.parity, newline.databits);
+	if (memcmp(&acm->line, &newline, sizeof newline)) {
+		memcpy(&acm->line, &newline, sizeof newline);
+		dbg("set line: %d %d %d %d", le32_to_cpu(newline.dwDTERate),
+			newline.bCharFormat, newline.bParityType,
+			newline.bDataBits);
 		acm_set_line(acm, &acm->line);
 	}
 }
@@ -522,7 +609,7 @@
 static int acm_probe (struct usb_interface *intf,
 		      const struct usb_device_id *id)
 {
-	struct union_desc *union_header = NULL;
+	struct usb_cdc_union_desc *union_header = NULL;
 	char *buffer = intf->altsetting->extra;
 	int buflen = intf->altsetting->extralen;
 	struct usb_interface *control_interface;
@@ -540,6 +627,7 @@
 	int call_interface_num = -1;
 	int data_interface_num;
 	unsigned long quirks;
+	int i;
 
 	/* handle quirks deadly to normal probing*/
 	quirks = (unsigned long)id->driver_info;
@@ -573,21 +661,22 @@
 		}
 
 		switch (buffer [2]) {
-			case CDC_UNION_TYPE: /* we've found it */
+			case USB_CDC_UNION_TYPE: /* we've found it */
 				if (union_header) {
 					err("More than one union descriptor, skipping ...");
 					goto next_desc;
 				}
-				union_header = (struct union_desc *)buffer;
+				union_header = (struct usb_cdc_union_desc *)
+							buffer;
 				break;
-			case CDC_COUNTRY_TYPE: /* maybe somehow export */
+			case USB_CDC_COUNTRY_TYPE: /* maybe somehow export */
 				break; /* for now we ignore it */
-			case CDC_HEADER_TYPE: /* maybe check version */ 
+			case USB_CDC_HEADER_TYPE: /* maybe check version */ 
 				break; /* for now we ignore it */ 
-			case CDC_AC_MANAGEMENT_TYPE:
+			case USB_CDC_ACM_TYPE:
 				ac_management_function = buffer[3];
 				break;
-			case CDC_CALL_MANAGEMENT_TYPE:
+			case USB_CDC_CALL_MANAGEMENT_TYPE:
 				call_management_function = buffer[3];
 				call_interface_num = buffer[4];
 				if ((call_management_function & 3) != 3)
@@ -679,7 +768,7 @@
 	memset(acm, 0, sizeof(struct acm));
 
 	ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize);
-	readsize = le16_to_cpu(epread->wMaxPacketSize);
+	readsize = le16_to_cpu(epread->wMaxPacketSize)*2;
 	acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize);
 	acm->control = control_interface;
 	acm->data = data_interface;
@@ -688,11 +777,14 @@
 	acm->ctrl_caps = ac_management_function;
 	acm->ctrlsize = ctrlsize;
 	acm->readsize = readsize;
-	acm->bh.func = acm_rx_tasklet;
-	acm->bh.data = (unsigned long) acm;
+	acm->urb_task.func = acm_rx_tasklet;
+	acm->urb_task.data = (unsigned long) acm;
 	INIT_WORK(&acm->work, acm_softint, acm);
 	spin_lock_init(&acm->throttle_lock);
 	acm->ready_for_write = 1;
+	acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress);
+
+	spin_lock_init(&acm->read_lock);
 
 	buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma);
 	if (!buf) {
@@ -701,13 +793,6 @@
 	}
 	acm->ctrl_buffer = buf;
 
-	buf = usb_buffer_alloc(usb_dev, readsize, GFP_KERNEL, &acm->read_dma);
-	if (!buf) {
-		dev_dbg(&intf->dev, "out of memory (read buffer alloc)\n");
-		goto alloc_fail3;
-	}
-	acm->read_buffer = buf;
-
 	buf = usb_buffer_alloc(usb_dev, acm->writesize, GFP_KERNEL, &acm->write_dma);
 	if (!buf) {
 		dev_dbg(&intf->dev, "out of memory (write buffer alloc)\n");
@@ -720,10 +805,24 @@
 		dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)\n");
 		goto alloc_fail5;
 	}
-	acm->readurb = usb_alloc_urb(0, GFP_KERNEL);
-	if (!acm->readurb) {
-		dev_dbg(&intf->dev, "out of memory (readurb kmalloc)\n");
-		goto alloc_fail6;
+	for (i = 0; i < ACM_MAX_READ_URBS; i++) {
+		struct acm_read_urb *rcv = &(acm->read_urbs[i]);
+
+		if (!(rcv->urb = usb_alloc_urb(0, GFP_KERNEL))) {
+			dev_dbg(&intf->dev, "out of memory (read_urbs kmalloc)\n");
+			goto alloc_fail7;
+		}
+
+		//rcv->urb->transfer_flags |= URB_NO_FSBR;
+		rcv->instance = acm;
+	}
+	for (i = 0; i < ACM_MAX_READ_BUFS; i++) {
+		struct acm_read_buffer *buf = &(acm->read_bufs[i]);
+
+		if (!(buf->base = kmalloc(readsize, GFP_KERNEL))) {
+			dev_dbg(&intf->dev, "out of memory (read_bufs kmalloc)\n");
+			goto alloc_fail7;
+		}
 	}
 	acm->writeurb = usb_alloc_urb(0, GFP_KERNEL);
 	if (!acm->writeurb) {
@@ -736,11 +835,6 @@
 	acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
 	acm->ctrlurb->transfer_dma = acm->ctrl_dma;
 
-	usb_fill_bulk_urb(acm->readurb, usb_dev, usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress),
-			  acm->read_buffer, readsize, acm_read_bulk, acm);
-	acm->readurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP;
-	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);
 	acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP;
@@ -750,10 +844,11 @@
 
 	acm_set_control(acm, acm->ctrlout);
 
-	acm->line.speed = cpu_to_le32(9600);
-	acm->line.databits = 8;
+	acm->line.dwDTERate = cpu_to_le32(9600);
+	acm->line.bDataBits = 8;
 	acm_set_line(acm, &acm->line);
 
+	acm_disconnected = 0;
 	usb_driver_claim_interface(&acm_driver, data_interface, acm);
 
 	tty_register_device(acm_tty_driver, minor, &intf->dev);
@@ -763,14 +858,14 @@
 	return 0;
 
 alloc_fail7:
-	usb_free_urb(acm->readurb);
-alloc_fail6:
+	for (i = 0; i < ACM_MAX_READ_BUFS; i++)
+		kfree(acm->read_bufs[i].base);
+	for (i = 0; i < ACM_MAX_READ_URBS; i++)
+		usb_free_urb(acm->read_urbs[i].urb);
 	usb_free_urb(acm->ctrlurb);
 alloc_fail5:
 	usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma);
 alloc_fail4:
-	usb_buffer_free(usb_dev, readsize, acm->read_buffer, acm->read_dma);
-alloc_fail3:
 	usb_buffer_free(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
 alloc_fail2:
 	kfree(acm);
@@ -782,33 +877,44 @@
 {
 	struct acm *acm = usb_get_intfdata (intf);
 	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	int i;
 
-	if (!acm || !acm->dev) {
+	if (!acm || !acm->dev || acm_disconnected) {
 		dbg("disconnect on nonexisting interface");
 		return;
 	}
 
 	down(&open_sem);
+	acm_disconnected = 1;
 	acm->dev = NULL;
 	usb_set_intfdata (intf, NULL);
 
+	tasklet_disable(&acm->urb_task);
+
 	usb_kill_urb(acm->ctrlurb);
-	usb_kill_urb(acm->readurb);
+	for (i = 0; i < ACM_MAX_READ_URBS; i++)
+		usb_kill_urb(acm->read_urbs[i].urb);
 	usb_kill_urb(acm->writeurb);
 
+	INIT_LIST_HEAD(&acm->filled_read_bufs);
+	INIT_LIST_HEAD(&acm->spare_read_bufs);
+
+	tasklet_enable(&acm->urb_task);
+
 	flush_scheduled_work(); /* wait for acm_softint */
 
 	usb_buffer_free(usb_dev, acm->writesize, acm->write_buffer, acm->write_dma);
-	usb_buffer_free(usb_dev, acm->readsize, acm->read_buffer, acm->read_dma);
+	for (i = 0; i < ACM_MAX_READ_BUFS; i++)
+		kfree(acm->read_bufs[i].base);
 	usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma);
 
 	usb_driver_release_interface(&acm_driver, acm->data);
 
 	if (!acm->used) {
-		tty_unregister_device(acm_tty_driver, acm->minor);
 		acm_table[acm->minor] = NULL;
 		usb_free_urb(acm->ctrlurb);
-		usb_free_urb(acm->readurb);
+		for (i = 0; i < ACM_MAX_READ_URBS; i++)
+			usb_free_urb(acm->read_urbs[i].urb);
 		usb_free_urb(acm->writeurb);
 		kfree(acm);
 		up(&open_sem);
@@ -831,14 +937,20 @@
 	.driver_info = NO_UNION_NORMAL, /* has no union descriptor */
 	},
 	/* control interfaces with various AT-command sets */
-	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 1) },
-	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 2) },
-	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 3) },
-	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 4) },
-	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 5) },
-	{ USB_INTERFACE_INFO(USB_CLASS_COMM, 2, 6) },
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+		USB_CDC_ACM_PROTO_AT_V25TER) },
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+		USB_CDC_ACM_PROTO_AT_PCCA101) },
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+		USB_CDC_ACM_PROTO_AT_PCCA101_WAKE) },
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+		USB_CDC_ACM_PROTO_AT_GSM) },
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+		USB_CDC_ACM_PROTO_AT_3G	) },
+	{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
+		USB_CDC_ACM_PROTO_AT_CDMA) },
 
-	/* NOTE:  COMM/2/0xff is likely MSFT RNDIS ... NOT a modem!! */
+	/* NOTE:  COMM/ACM/0xff is likely MSFT RNDIS ... NOT a modem!! */
 	{ }
 };
 
--- linux-2.6.11.11/drivers/usb/class/cdc-acm.h	2005-05-27 07:06:46.000000000 +0200
+++ linux-2.6.12-rc5-dave/drivers/usb/class/cdc-acm.h	2005-06-07 11:41:44.000000000 +0200
@@ -27,24 +27,6 @@
 
 #define USB_RT_ACM		(USB_TYPE_CLASS | USB_RECIP_INTERFACE)
 
-#define ACM_REQ_COMMAND		0x00
-#define ACM_REQ_RESPONSE	0x01
-#define ACM_REQ_SET_FEATURE	0x02
-#define ACM_REQ_GET_FEATURE	0x03
-#define ACM_REQ_CLEAR_FEATURE	0x04
-
-#define ACM_REQ_SET_LINE	0x20
-#define ACM_REQ_GET_LINE	0x21
-#define ACM_REQ_SET_CONTROL	0x22
-#define ACM_REQ_SEND_BREAK	0x23
-
-/*
- * IRQs.
- */
-
-#define ACM_IRQ_NETWORK		0x00
-#define ACM_IRQ_LINE_STATE	0x20
-
 /*
  * Output control lines.
  */
@@ -66,15 +48,24 @@
 #define ACM_CTRL_OVERRUN	0x40
 
 /*
- * Line speed and caracter encoding.
+ * Transfers
  */
 
-struct acm_line {
-	__le32 speed;
-	__u8 stopbits;
-	__u8 parity;
-	__u8 databits;
-} __attribute__ ((packed));
+#define ACM_MAX_READ_URBS	16
+#define ACM_MAX_READ_BUFS	16
+
+struct acm_read_buffer {
+	struct list_head	list;
+	int			size;
+	unsigned char		*base;
+};
+
+struct acm_read_urb {
+	struct list_head	list;
+	struct acm_read_buffer	*buffer;
+	struct urb		*urb;
+	struct acm		*instance;
+};
 
 /*
  * Internal driver structures.
@@ -85,12 +76,19 @@
 	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 */
+	struct urb *ctrlurb, *writeurb;			/* urbs */
+	u8 *ctrl_buffer, *write_buffer;			/* buffers of urbs */
+	int rx_endpoint;
+	struct acm_read_urb read_urbs[ACM_MAX_READ_URBS];
+	struct acm_read_buffer read_bufs[ACM_MAX_READ_BUFS];
+	spinlock_t read_lock;
+	struct list_head spare_read_urbs;
+	struct list_head spare_read_bufs;
+	struct list_head filled_read_bufs;
 	dma_addr_t ctrl_dma, read_dma, write_dma;	/* dma handles of buffers */
-	struct acm_line line;				/* line coding (bits, stop, parity) */
+	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 */
+	struct tasklet_struct urb_task;			/* rx processing */
 	spinlock_t throttle_lock;			/* synchronize throtteling and read callback */
 	unsigned int ctrlin;				/* input control lines (DCD, DSR, RI, break, overruns) */
 	unsigned int ctrlout;				/* output control lines (DTR, RTS) */
@@ -101,28 +99,9 @@
 	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 */
 };
 
-/* "Union Functional Descriptor" from CDC spec 5.2.3.X */
-struct union_desc {
-	u8	bLength;
-	u8	bDescriptorType;
-	u8	bDescriptorSubType;
-
-	u8	bMasterInterface0;
-	u8	bSlaveInterface0;
-	/* ... and there could be other slave interfaces */
-} __attribute__ ((packed));
-
-/* class specific descriptor types */
-#define CDC_HEADER_TYPE			0x00
-#define CDC_CALL_MANAGEMENT_TYPE	0x01
-#define CDC_AC_MANAGEMENT_TYPE		0x02
-#define CDC_UNION_TYPE			0x06
-#define CDC_COUNTRY_TYPE		0x07
-
 #define CDC_DATA_INTERFACE_TYPE	0x0a
 
 /* constants describing various quirks and errors */
--- linux-2.6.11.11/include/linux/usb_cdc.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.12-rc5-dave/include/linux/usb_cdc.h	2005-06-06 16:17:21.000000000 +0200
@@ -0,0 +1,192 @@
+/*
+ * USB Communications Device Class (CDC) definitions
+ *
+ * CDC says how to talk to lots of different types of network adapters,
+ * notably ethernet adapters and various modems.  It's used mostly with
+ * firmware based USB peripherals.
+ */
+
+#define USB_CDC_SUBCLASS_ACM			0x02
+#define USB_CDC_SUBCLASS_ETHERNET		0x06
+#define USB_CDC_SUBCLASS_WHCM			0x08
+#define USB_CDC_SUBCLASS_DMM			0x09
+#define USB_CDC_SUBCLASS_MDLM			0x0a
+#define USB_CDC_SUBCLASS_OBEX			0x0b
+
+#define USB_CDC_PROTO_NONE			0
+
+#define USB_CDC_ACM_PROTO_AT_V25TER		1
+#define USB_CDC_ACM_PROTO_AT_PCCA101		2
+#define USB_CDC_ACM_PROTO_AT_PCCA101_WAKE	3
+#define USB_CDC_ACM_PROTO_AT_GSM		4
+#define USB_CDC_ACM_PROTO_AT_3G			5
+#define USB_CDC_ACM_PROTO_AT_CDMA		6
+#define USB_CDC_ACM_PROTO_VENDOR		0xff
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Class-Specific descriptors ... there are a couple dozen of them
+ */
+
+#define USB_CDC_HEADER_TYPE		0x00		/* header_desc */
+#define USB_CDC_CALL_MANAGEMENT_TYPE	0x01		/* call_mgmt_descriptor */
+#define USB_CDC_ACM_TYPE		0x02		/* acm_descriptor */
+#define USB_CDC_UNION_TYPE		0x06		/* union_desc */
+#define USB_CDC_COUNTRY_TYPE		0x07
+#define USB_CDC_ETHERNET_TYPE		0x0f		/* ether_desc */
+#define USB_CDC_WHCM_TYPE		0x11
+#define USB_CDC_MDLM_TYPE		0x12		/* mdlm_desc */
+#define USB_CDC_MDLM_DETAIL_TYPE	0x13		/* mdlm_detail_desc */
+#define USB_CDC_DMM_TYPE		0x14
+#define USB_CDC_OBEX_TYPE		0x15
+
+/* "Header Functional Descriptor" from CDC spec  5.2.3.1 */
+struct usb_cdc_header_desc {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDescriptorSubType;
+
+	__le16	bcdCDC;
+} __attribute__ ((packed));
+
+/* "Call Management Descriptor" from CDC spec  5.2.3.2 */
+struct usb_cdc_call_mgmt_descriptor {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDescriptorSubType;
+
+	__u8	bmCapabilities;
+#define USB_CDC_CALL_MGMT_CAP_CALL_MGMT		0x01
+#define USB_CDC_CALL_MGMT_CAP_DATA_INTF		0x02
+
+	__u8	bDataInterface;
+} __attribute__ ((packed));
+
+/* "Abstract Control Management Descriptor" from CDC spec  5.2.3.3 */
+struct usb_cdc_acm_descriptor {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDescriptorSubType;
+
+	__u8	bmCapabilities;
+} __attribute__ ((packed));
+
+/* "Union Functional Descriptor" from CDC spec 5.2.3.8 */
+struct usb_cdc_union_desc {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDescriptorSubType;
+
+	__u8	bMasterInterface0;
+	__u8	bSlaveInterface0;
+	/* ... and there could be other slave interfaces */
+} __attribute__ ((packed));
+
+/* "Ethernet Networking Functional Descriptor" from CDC spec 5.2.3.16 */
+struct usb_cdc_ether_desc {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDescriptorSubType;
+
+	__u8	iMACAddress;
+	__le32	bmEthernetStatistics;
+	__le16	wMaxSegmentSize;
+	__le16	wNumberMCFilters;
+	__u8	bNumberPowerFilters;
+} __attribute__ ((packed));
+
+/* "MDLM Functional Descriptor" from CDC WMC spec 6.7.2.3 */
+struct usb_cdc_mdlm_desc {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDescriptorSubType;
+
+	__le16	bcdVersion;
+	__u8	bGUID[16];
+} __attribute__ ((packed));
+
+/* "MDLM Detail Functional Descriptor" from CDC WMC spec 6.7.2.4 */
+struct usb_cdc_mdlm_detail_desc {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDescriptorSubType;
+
+	/* type is associated with mdlm_desc.bGUID */
+	__u8	bGuidDescriptorType;
+	__u8	bDetailData[0];
+} __attribute__ ((packed));
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Class-Specific Control Requests (6.2)
+ *
+ * section 3.6.2.1 table 4 has the ACM profile, for modems.
+ * section 3.8.2 table 10 has the ethernet profile.
+ *
+ * Microsoft's RNDIS stack for Ethernet is a vendor-specific CDC ACM variant,
+ * heavily dependent on the encapsulated (proprietary) command mechanism.
+ */
+
+#define USB_CDC_SEND_ENCAPSULATED_COMMAND	0x00
+#define USB_CDC_GET_ENCAPSULATED_RESPONSE	0x01
+#define USB_CDC_REQ_SET_LINE_CODING		0x20
+#define USB_CDC_REQ_GET_LINE_CODING		0x21
+#define USB_CDC_REQ_SET_CONTROL_LINE_STATE	0x22
+#define USB_CDC_REQ_SEND_BREAK			0x23
+#define USB_CDC_SET_ETHERNET_MULTICAST_FILTERS	0x40
+#define USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER	0x41
+#define USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER	0x42
+#define USB_CDC_SET_ETHERNET_PACKET_FILTER	0x43
+#define USB_CDC_GET_ETHERNET_STATISTIC		0x44
+
+/* Line Coding Structure from CDC spec 6.2.13 */
+struct usb_cdc_line_coding {
+	__le32	dwDTERate;
+	__u8	bCharFormat;
+#define USB_CDC_1_STOP_BITS			0
+#define USB_CDC_1_5_STOP_BITS			1
+#define USB_CDC_2_STOP_BITS			2
+
+	__u8	bParityType;
+#define USB_CDC_NO_PARITY			0
+#define USB_CDC_ODD_PARITY			1
+#define USB_CDC_EVEN_PARITY			2
+#define USB_CDC_MARK_PARITY			3
+#define USB_CDC_SPACE_PARITY			4
+
+	__u8	bDataBits;
+} __attribute__ ((packed));
+
+/* table 62; bits in multicast filter */
+#define	USB_CDC_PACKET_TYPE_PROMISCUOUS		(1 << 0)
+#define	USB_CDC_PACKET_TYPE_ALL_MULTICAST	(1 << 1) /* no filter */
+#define	USB_CDC_PACKET_TYPE_DIRECTED		(1 << 2)
+#define	USB_CDC_PACKET_TYPE_BROADCAST		(1 << 3)
+#define	USB_CDC_PACKET_TYPE_MULTICAST		(1 << 4) /* filtered */
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Class-Specific Notifications (6.3) sent by interrupt transfers
+ *
+ * section 3.8.2 table 11 of the CDC spec lists Ethernet notifications
+ * section 3.6.2.1 table 5 specifies ACM notifications, accepted by RNDIS
+ * RNDIS also defines its own bit-incompatible notifications
+ */
+
+#define USB_CDC_NOTIFY_NETWORK_CONNECTION	0x00
+#define USB_CDC_NOTIFY_RESPONSE_AVAILABLE	0x01
+#define USB_CDC_NOTIFY_SERIAL_STATE		0x20
+#define USB_CDC_NOTIFY_SPEED_CHANGE		0x2a
+
+struct usb_cdc_notification {
+	__u8	bmRequestType;
+	__u8	bNotificationType;
+	__le16	wValue;
+	__le16	wIndex;
+	__le16	wLength;
+} __attribute__ ((packed));
+


Další informace o konferenci Talk