[RFC] [PATCH] usbatm.[ch]: multiple changes

Roman Kagan rkagan at mail.ru
Tue Mar 29 07:04:45 EST 2005


  Hi,

As Duncan didn't stop me I've gone wild changing usbatm...

Below is the patch against current CVS.  It's probably too big to be
comprehensible, let me know if I'd better send the complete source files
instead.

The summary of the proposed changes:

1) To avoid duplicating a lot of code for rx and tx paths, I introduce
   struct usbatm_channel, so that all buffer management and error
   handling is direction agnostic.

2) Structs usbatm_{receiver,sender,receive_buffer,send_buffer} are gone.
   Instead I use struct usbatm_transceiver which is basically a wrapper
   around an urb.
   
3) All the urb-related stuff (buffer and its size, urb status, etc.) is
   now contained in the urb itself.  This makes it trivial to add iso
   transport for rx (which I'm going to do later today).

4) usbatm_{extract,write}_cells and usbatm_{r,t}x_process are reworked
   to manipulate data sizes in bytes rather then cells: it removes a lot
   of unneeded multiplications and divisions; for safety I've added a
   couple of assertions to make sure the data sizes are aligned on
   transfer block size (cell size + padding).
   
5) usbatm_write_cells code is restructured to be somewhat more
   transparent, and unnecessary memsetting of the usb padding is
   removed.

6) Redundant usb_fill_*_urb are removed; only the modified information
   (->transfer_buffer_length) is updated before each submission.

7) I introduced usb error handling for each channel as follows:

   a) in case of a submission or completion error, the rescheduling of
      the correspoding tasklet is delayed by THROTTLE_MSECS (== 5);

   b) each error decrements the num_allowed_errors atomic counter, and
      each success resets it to USBATM_MAX_ERRORS (== 1024); once it
      reaches zero the channel is suspended;

   c) -EPIPE error causes scheduling of the clear_halt_work, which calls
      usb_clear_halt on the correspoding endpoint in the process
      context.

   Please note that error handling, and especially clear_halt path is
   not well tested as I don't know how to provoke this kind of errors.
   The only sort of errors I could generate were those occurring at
   disconnection, and they seem to be OK.

8) Introduced simple OAM F5 end-to-end loopback replies.

Although I seem to have removed no existing functionality, the overall
code size shrunk, both in terms of the source code and object code:

 usbatm.c |  847 +++++++++++++++++++++++++++++----------------------------------
 usbatm.h |   86 +-----
 2 files changed, 408 insertions(+), 525 deletions(-)

   text	   data	    bss	    dec	    hex	filename
   9496	    992	      0	  10488	   28f8	/tmp/usbatm.ko
  10243	    404	     48	  10695	   29c7	/tmp/usbatm.ko.orig

Note that a big space eater is the crc10 lookup table used for OAM; I
think it should eventually go into lib/crc10 in the kernel tree.

After fixing a few obvious typos, the code runs on my machine for
several hours and hasn't crashed it so far :)

Comments are very welcome.
Cheers,
  Roman.

Index: usbatm.h
===================================================================
RCS file: /home/cvs/usbatm/usbatm.h,v
retrieving revision 1.10
diff -u -r1.10 usbatm.h
--- usbatm.h	7 Feb 2005 23:50:41 -0000	1.10
+++ usbatm.h	29 Mar 2005 02:51:22 -0000
@@ -31,6 +31,8 @@
 #include <linux/config.h>
 #include <linux/kref.h>
 #include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
 
 /*
 #define DEBUG
@@ -95,55 +97,25 @@
 extern void usbatm_usb_disconnect(struct usb_interface *intf);
 
 
-/* usbatm */
-
-#define UDSL_MAX_RCV_URBS		4
-#define UDSL_MAX_SND_URBS		4
-#define UDSL_MAX_RCV_BUFS		8
-#define UDSL_MAX_SND_BUFS		8
-#define UDSL_MAX_RCV_BUF_SIZE		1024	/* ATM cells */
-#define UDSL_MAX_SND_BUF_SIZE		1024	/* ATM cells */
-#define UDSL_DEFAULT_RCV_URBS		2
-#define UDSL_DEFAULT_SND_URBS		2
-#define UDSL_DEFAULT_RCV_BUFS		4
-#define UDSL_DEFAULT_SND_BUFS		4
-#define UDSL_DEFAULT_RCV_BUF_SIZE	64	/* ATM cells */
-#define UDSL_DEFAULT_SND_BUF_SIZE	64	/* ATM cells */
-
-
-/* receive */
-
-struct usbatm_receive_buffer {
+struct usbatm_channel {
+	int endpoint;			/* usb pipe */
+	unsigned int stride;		/* ATM cell size + padding */
+	unsigned int buf_size;		/* urb buffer size */
+	spinlock_t lock;
 	struct list_head list;
-	unsigned char *base;
-	unsigned int filled_cells;
+	struct tasklet_struct tasklet;
+	struct timer_list delay;
+	struct work_struct clear_halt_work;
+	atomic_t num_allowed_errors;
+	struct usbatm_data *usbatm;
 };
 
-struct usbatm_receiver {
-	struct list_head list;
-	struct usbatm_receive_buffer *buffer;
+struct usbatm_transceiver {
 	struct urb *urb;
-	struct usbatm_data *instance;
-};
-
-
-/* send */
-
-struct usbatm_send_buffer {
 	struct list_head list;
-	unsigned char *base;
-	unsigned char *free_start;
-	unsigned int free_cells;
+	struct usbatm_channel *channel;
 };
 
-struct usbatm_sender {
-	struct list_head list;
-	struct usbatm_send_buffer *buffer;
-	struct urb *urb;
-	struct usbatm_data *instance;
-};
-
-
 /* main driver data */
 
 struct usbatm_data {
@@ -160,10 +132,6 @@
 	struct usb_device *usb_dev;
 	struct usb_interface *usb_intf;
 	char description[64];
-	int tx_endpoint;
-	int rx_endpoint;
-	int tx_padding;
-	int rx_padding;
 
 	/* ATM device */
 	struct atm_dev *atm_dev;
@@ -183,31 +151,13 @@
 	/* ATM device */
 	struct list_head vcc_list;
 
-	/* receive */
-	struct usbatm_receiver receivers[UDSL_MAX_RCV_URBS];
-	struct usbatm_receive_buffer receive_buffers[UDSL_MAX_RCV_BUFS];
-
-	spinlock_t receive_lock;
-	struct list_head spare_receivers;
-	struct list_head filled_receive_buffers;
-
-	struct tasklet_struct receive_tasklet;
-	struct list_head spare_receive_buffers;
-
-	/* send */
-	struct usbatm_sender senders[UDSL_MAX_SND_URBS];
-	struct usbatm_send_buffer send_buffers[UDSL_MAX_SND_BUFS];
-
-	struct sk_buff_head sndqueue;
+	struct usbatm_transceiver *transceivers;
 
-	spinlock_t send_lock;
-	struct list_head spare_senders;
-	struct list_head spare_send_buffers;
+	struct usbatm_channel rx_channel;
+	struct usbatm_channel tx_channel;
 
-	struct tasklet_struct send_tasklet;
+	struct sk_buff_head sndqueue;
 	struct sk_buff *current_skb;			/* being emptied */
-	struct usbatm_send_buffer *current_buffer;	/* being filled */
-	struct list_head filled_send_buffers;
 };
 
 #endif	/* _USBATM_H_ */
Index: usbatm.c
===================================================================
RCS file: /home/cvs/usbatm/usbatm.c,v
retrieving revision 1.15
diff -u -r1.15 usbatm.c
--- usbatm.c	4 Mar 2005 11:30:58 -0000	1.15
+++ usbatm.c	29 Mar 2005 02:51:23 -0000
@@ -95,22 +95,19 @@
 
 static const char usbatm_driver_name[] = "usbatm";
 
-#define UDSL_MAX_RCV_URBS		4
-#define UDSL_MAX_SND_URBS		4
-#define UDSL_MAX_RCV_BUFS		8
-#define UDSL_MAX_SND_BUFS		8
+#define UDSL_MAX_RCV_URBS		16
+#define UDSL_MAX_SND_URBS		16
 #define UDSL_MAX_RCV_BUF_SIZE		1024	/* ATM cells */
 #define UDSL_MAX_SND_BUF_SIZE		1024	/* ATM cells */
-#define UDSL_DEFAULT_RCV_URBS		2
-#define UDSL_DEFAULT_SND_URBS		2
-#define UDSL_DEFAULT_RCV_BUFS		4
-#define UDSL_DEFAULT_SND_BUFS		4
+#define UDSL_DEFAULT_RCV_URBS		4
+#define UDSL_DEFAULT_SND_URBS		4
 #define UDSL_DEFAULT_RCV_BUF_SIZE	64	/* ATM cells */
 #define UDSL_DEFAULT_SND_BUF_SIZE	64	/* ATM cells */
 
 #define ATM_CELL_HEADER			(ATM_CELL_SIZE - ATM_CELL_PAYLOAD)
-#define UDSL_NUM_CELLS(x)		(((x) + ATM_AAL5_TRAILER + ATM_CELL_PAYLOAD - 1) / ATM_CELL_PAYLOAD)
 
+#define THROTTLE_MSECS			5	/* delay to recover processing after urb submission fails */
+#define USBATM_MAX_ERRORS		1024	/* number of consecutive usb errors to tolerate */
 
 /* receive */
 
@@ -129,11 +126,9 @@
 /* send */
 
 struct usbatm_control {
-	struct atm_skb_data atm_data;
-	unsigned int num_cells;
-	unsigned int num_entire;
-	unsigned int pdu_padding;
-	unsigned char aal5_trailer[ATM_AAL5_TRAILER];
+	struct atm_vcc *vcc;
+	u32 len;
+	u32 crc;
 };
 
 #define UDSL_SKB(x)		((struct usbatm_control *)(x)->cb)
@@ -141,8 +136,6 @@
 
 static unsigned int num_rcv_urbs = UDSL_DEFAULT_RCV_URBS;
 static unsigned int num_snd_urbs = UDSL_DEFAULT_SND_URBS;
-static unsigned int num_rcv_bufs = UDSL_DEFAULT_RCV_BUFS;
-static unsigned int num_snd_bufs = UDSL_DEFAULT_SND_BUFS;
 static unsigned int rcv_buf_size = UDSL_DEFAULT_RCV_BUF_SIZE;
 static unsigned int snd_buf_size = UDSL_DEFAULT_SND_BUF_SIZE;
 
@@ -158,27 +151,15 @@
 		 __MODULE_STRING(UDSL_MAX_SND_URBS) ", default: "
 		 __MODULE_STRING(UDSL_DEFAULT_SND_URBS) ")");
 
-module_param(num_rcv_bufs, uint, 0444);
-MODULE_PARM_DESC(num_rcv_bufs,
-		 "Number of buffers used for reception (range: 0-"
-		 __MODULE_STRING(UDSL_MAX_RCV_BUFS) ", default: "
-		 __MODULE_STRING(UDSL_DEFAULT_RCV_BUFS) ")");
-
-module_param(num_snd_bufs, uint, 0444);
-MODULE_PARM_DESC(num_snd_bufs,
-		 "Number of buffers used for transmission (range: 0-"
-		 __MODULE_STRING(UDSL_MAX_SND_BUFS) ", default: "
-		 __MODULE_STRING(UDSL_DEFAULT_SND_BUFS) ")");
-
 module_param(rcv_buf_size, uint, 0444);
 MODULE_PARM_DESC(rcv_buf_size,
-		 "Size of the buffers used for reception (range: 0-"
+		 "Size of the buffers used for reception in ATM cells (range: 0-"
 		 __MODULE_STRING(UDSL_MAX_RCV_BUF_SIZE) ", default: "
 		 __MODULE_STRING(UDSL_DEFAULT_RCV_BUF_SIZE) ")");
 
 module_param(snd_buf_size, uint, 0444);
 MODULE_PARM_DESC(snd_buf_size,
-		 "Size of the buffers used for transmission (range: 0-"
+		 "Size of the buffers used for transmission in ATM cells (range: 0-"
 		 __MODULE_STRING(UDSL_MAX_SND_BUF_SIZE) ", default: "
 		 __MODULE_STRING(UDSL_DEFAULT_SND_BUF_SIZE) ")");
 
@@ -205,6 +186,11 @@
 /***********
 **  misc  **
 ***********/
+static inline unsigned int usbatm_pdu_length(unsigned int length)
+{
+	length += ATM_CELL_PAYLOAD - 1 + ATM_AAL5_TRAILER;
+	return length - length % ATM_CELL_PAYLOAD;
+}
 
 static inline void usbatm_pop(struct atm_vcc *vcc, struct sk_buff *skb)
 {
@@ -214,6 +200,155 @@
 		dev_kfree_skb(skb);
 }
 
+static u16 crc10_table[256] = {
+	0x000, 0x233, 0x255, 0x066, 0x299, 0x0aa, 0x0cc, 0x2ff, 0x301, 0x132, 0x154, 0x367, 0x198, 0x3ab, 0x3cd, 0x1fe,
+	0x031, 0x202, 0x264, 0x057, 0x2a8, 0x09b, 0x0fd, 0x2ce, 0x330, 0x103, 0x165, 0x356, 0x1a9, 0x39a, 0x3fc, 0x1cf,
+	0x062, 0x251, 0x237, 0x004, 0x2fb, 0x0c8, 0x0ae, 0x29d, 0x363, 0x150, 0x136, 0x305, 0x1fa, 0x3c9, 0x3af, 0x19c,
+	0x053, 0x260, 0x206, 0x035, 0x2ca, 0x0f9, 0x09f, 0x2ac, 0x352, 0x161, 0x107, 0x334, 0x1cb, 0x3f8, 0x39e, 0x1ad,
+	0x0c4, 0x2f7, 0x291, 0x0a2, 0x25d, 0x06e, 0x008, 0x23b, 0x3c5, 0x1f6, 0x190, 0x3a3, 0x15c, 0x36f, 0x309, 0x13a,
+	0x0f5, 0x2c6, 0x2a0, 0x093, 0x26c, 0x05f, 0x039, 0x20a, 0x3f4, 0x1c7, 0x1a1, 0x392, 0x16d, 0x35e, 0x338, 0x10b,
+	0x0a6, 0x295, 0x2f3, 0x0c0, 0x23f, 0x00c, 0x06a, 0x259, 0x3a7, 0x194, 0x1f2, 0x3c1, 0x13e, 0x30d, 0x36b, 0x158,
+	0x097, 0x2a4, 0x2c2, 0x0f1, 0x20e, 0x03d, 0x05b, 0x268, 0x396, 0x1a5, 0x1c3, 0x3f0, 0x10f, 0x33c, 0x35a, 0x169,
+	0x188, 0x3bb, 0x3dd, 0x1ee, 0x311, 0x122, 0x144, 0x377, 0x289, 0x0ba, 0x0dc, 0x2ef, 0x010, 0x223, 0x245, 0x076,
+	0x1b9, 0x38a, 0x3ec, 0x1df, 0x320, 0x113, 0x175, 0x346, 0x2b8, 0x08b, 0x0ed, 0x2de, 0x021, 0x212, 0x274, 0x047,
+	0x1ea, 0x3d9, 0x3bf, 0x18c, 0x373, 0x140, 0x126, 0x315, 0x2eb, 0x0d8, 0x0be, 0x28d, 0x072, 0x241, 0x227, 0x014,
+	0x1db, 0x3e8, 0x38e, 0x1bd, 0x342, 0x171, 0x117, 0x324, 0x2da, 0x0e9, 0x08f, 0x2bc, 0x043, 0x270, 0x216, 0x025,
+	0x14c, 0x37f, 0x319, 0x12a, 0x3d5, 0x1e6, 0x180, 0x3b3, 0x24d, 0x07e, 0x018, 0x22b, 0x0d4, 0x2e7, 0x281, 0x0b2,
+	0x17d, 0x34e, 0x328, 0x11b, 0x3e4, 0x1d7, 0x1b1, 0x382, 0x27c, 0x04f, 0x029, 0x21a, 0x0e5, 0x2d6, 0x2b0, 0x083,
+	0x12e, 0x31d, 0x37b, 0x148, 0x3b7, 0x184, 0x1e2, 0x3d1, 0x22f, 0x01c, 0x07a, 0x249, 0x0b6, 0x285, 0x2e3, 0x0d0,
+	0x11f, 0x32c, 0x34a, 0x179, 0x386, 0x1b5, 0x1d3, 0x3e0, 0x21e, 0x02d, 0x04b, 0x278, 0x087, 0x2b4, 0x2d2, 0x0e1,
+};
+
+static u16 inline crc10(u16 init, u8 *data, int len)
+{
+	while (len--)
+		init = ((init << 8) & 0x3ff) ^ crc10_table[(init >> 2) & 0xff] ^ *data++;
+	return init;
+}
+
+/* error limit */
+static int usbatm_check_usb_errcode(struct usbatm_channel *channel, int errcode)
+{
+	dbg("usbatm_check_usb_errcode: channel 0x%p, errcode %d", channel, errcode);
+
+	if (likely(!errcode)) {
+		/* reset allowed errors counter */
+		atomic_set(&channel->num_allowed_errors, USBATM_MAX_ERRORS);
+		return errcode;
+	}
+
+	if (atomic_dec_and_test(&channel->num_allowed_errors)) {
+		dev_err(&channel->usbatm->usb_intf->dev,
+			"too many consecutive usb errors, suspending data transfers.\n");
+		tasklet_disable(&channel->tasklet);
+	}
+
+	if (errcode == -EPIPE)		/* this one may be cleaned up at times */
+		schedule_work(&channel->clear_halt_work);
+
+	return errcode;
+}
+
+/* buffer management */
+static inline struct usbatm_transceiver *usbatm_pop_transceiver(struct usbatm_channel *channel)
+{
+	struct usbatm_transceiver *trx;
+
+	spin_lock_irq(&channel->lock);
+	if (list_empty(&channel->list)) {
+		spin_unlock_irq(&channel->lock);
+		return NULL;
+	}
+	
+	trx = list_entry(channel->list.next, struct usbatm_transceiver, list);
+	list_del(&trx->list);
+	spin_unlock_irq(&channel->lock);
+
+	return trx;
+}
+
+static inline int usbatm_submit(struct usbatm_transceiver *trx)
+{
+	int ret;
+	struct usbatm_channel *channel = trx->channel;
+
+	vdbg("usbatm_submit: submitting urb 0x%p, trx 0x%p, size %u",
+	     trx->urb, trx, trx->urb->transfer_buffer_length);
+
+	ret = usb_submit_urb(trx->urb, GFP_ATOMIC);
+	if (ret) {
+		dbg("usbatm_submit: trx 0x%p urb 0x%p submission failed (%d)!",
+		    trx, trx->urb, ret);
+
+		/* consider all errors transient and return the buffer back to the queue */
+		spin_lock_irq(&channel->lock);
+		list_add(&trx->list, &channel->list);
+		spin_unlock_irq(&channel->lock);
+
+		/* make sure the channel doesn't stall */
+		mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS));
+	}
+
+	return usbatm_check_usb_errcode(channel, ret);
+}
+
+static void usbatm_complete(struct urb *urb, struct pt_regs *regs)
+{
+	struct usbatm_transceiver *trx = urb->context;
+	struct usbatm_channel *channel = trx->channel;
+	unsigned long flags;
+
+	vdbg("usbatm_complete: urb 0x%p, status %d, actual_length %d, trx 0x%p",
+	     urb, urb->status, urb->actual_length, trx);
+
+	/* may be in_interrupt() */
+	spin_lock_irqsave(&channel->lock, flags);
+	list_add_tail(&trx->list, &channel->list);
+	spin_unlock_irqrestore(&channel->lock, flags);
+
+	/* throttle processing in case of an error */
+	if (usbatm_check_usb_errcode(channel, urb->status))
+		mod_timer(&channel->delay, jiffies + msecs_to_jiffies(THROTTLE_MSECS));
+	else
+		tasklet_schedule(&channel->tasklet);
+}
+
+/* OAM loopback reply (fire-and-forget) */
+static int usbatm_oam_reply(struct usbatm_data *instance, u8 *source)
+{
+	struct usbatm_transceiver *tx;
+	u16 crc;
+	u8 *buffer;
+
+	if (source[ATM_CELL_HEADER] != 0x18 || source[ATM_CELL_HEADER + 1] != 0x01) {
+		dbg("usbatm_oam_reply: unexpected OAM type/function %x direction %x !", source[5], source[6]);
+		return -EPROTO;
+	}
+
+	if (crc10(0, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD)) {
+		dbg("usbatm_oam_reply: OAM CRC10 error!");
+		return -EIO;
+	}
+
+	tx = usbatm_pop_transceiver(&instance->tx_channel);
+	if (!tx)
+		return -ENOMEM;
+	
+	buffer = tx->urb->transfer_buffer;
+	memcpy(buffer, source, ATM_CELL_SIZE);
+
+	buffer[ATM_CELL_HEADER + 1] = 0;	/* update the direction field */
+	
+	memset(buffer + ATM_CELL_SIZE - 2, 0, 2);
+	
+	crc = crc10(0, buffer + ATM_CELL_HEADER, ATM_CELL_PAYLOAD);
+	buffer[ATM_CELL_SIZE - 2] = (crc & 0x3) << 8;
+	buffer[ATM_CELL_SIZE - 1] = crc & 0xff;
+
+	tx->urb->transfer_buffer_length = tx->channel->stride;
+	return usbatm_submit(tx);
+}
+
 /*************
 **  decode  **
 *************/
@@ -230,24 +365,24 @@
 }
 
 static void usbatm_extract_cells(struct usbatm_data *instance,
-			       unsigned char *source, unsigned int howmany)
+				 unsigned char *source, unsigned int avail_data)
 {
 	struct usbatm_vcc_data *cached_vcc = NULL;
 	struct atm_vcc *vcc;
 	struct sk_buff *sarb;
 	struct usbatm_vcc_data *vcc_data;
-	int cached_vci = 0;
-	unsigned int i;
-	int pti;
-	int vci;
-	short cached_vpi = 0;
-	short vpi;
+	u8 pti;
+	u32 vci, cached_vci = 0;
+	u16 vpi, cached_vpi = 0;
+
+	/* UDSL_ASSERT(!(avail_data % instance->rx_channel.stride)); */
+	BUG_ON(avail_data % instance->rx_channel.stride);
 
-	for (i = 0; i < howmany;
-	     i++, source += ATM_CELL_SIZE + instance->rx_padding) {
+	for (; avail_data;
+	     avail_data -= instance->rx_channel.stride, source += instance->rx_channel.stride) {
 		vpi = ((source[0] & 0x0f) << 4) | (source[1] >> 4);
 		vci = ((source[1] & 0x0f) << 12) | (source[2] << 4) | (source[3] >> 4);
-		pti = (source[3] & 0x2) != 0;
+		pti = ((source[3] & 0xe) >> 1);
 
 		vdbg("usbatm_extract_cells: vpi %hd, vci %d, pti %d", vpi, vci, pti);
 
@@ -263,6 +398,16 @@
 		}
 
 		vcc = vcc_data->vcc;
+
+		/* OAM F5 end-to-end */
+		if (pti == ATM_PTI_E2EF5) {
+			if (usbatm_oam_reply(instance, source)) {
+				dbg("usbatm_extract_cells: OAM reply failed (vcc: 0x%p)!", vcc);
+				atomic_inc(&vcc->stats->rx_err);
+			}
+			continue;
+		}
+
 		sarb = vcc_data->sarb;
 
 		if (sarb->tail + ATM_CELL_PAYLOAD > sarb->end) {
@@ -274,7 +419,7 @@
 		memcpy(sarb->tail, source + ATM_CELL_HEADER, ATM_CELL_PAYLOAD);
 		__skb_put(sarb, ATM_CELL_PAYLOAD);
 
-		if (pti) {
+		if (pti & 1) {
 			struct sk_buff *skb;
 			unsigned int length;
 			unsigned int pdu_length;
@@ -288,7 +433,7 @@
 				goto out;
 			}
 
-			pdu_length = UDSL_NUM_CELLS(length) * ATM_CELL_PAYLOAD;
+			pdu_length = usbatm_pdu_length(length);
 
 			if (sarb->len < pdu_length) {
 				dbg("usbatm_extract_cells: bogus pdu_length %u (sarb->len: %u, vcc: 0x%p)!", pdu_length, sarb->len, vcc);
@@ -338,342 +483,142 @@
 **  encode  **
 *************/
 
-static inline void usbatm_fill_cell_header(unsigned char *target, struct atm_vcc *vcc)
-{
-	target[0] = vcc->vpi >> 4;
-	target[1] = (vcc->vpi << 4) | (vcc->vci >> 12);
-	target[2] = vcc->vci >> 4;
-	target[3] = vcc->vci << 4;
-	target[4] = 0xec;
-}
-
-static const unsigned char zeros[ATM_CELL_PAYLOAD];
-
-static void usbatm_groom_skb(struct atm_vcc *vcc, struct sk_buff *skb)
+static unsigned int usbatm_write_cells(struct usbatm_data *instance,
+				       struct sk_buff *skb,
+				       u8 *target, unsigned int avail_space)
 {
 	struct usbatm_control *ctrl = UDSL_SKB(skb);
-	unsigned int zero_padding;
-	u32 crc;
+	unsigned int num_written;
 
-	ctrl->atm_data.vcc = vcc;
+	/* UDSL_ASSERT(!(avail_space % instance->tx_channel.stride)); */
+	BUG_ON(avail_space % instance->tx_channel.stride);
+	vdbg("usbatm_write_cells: skb->len=%d, avail_space=%u",
+	     skb->len, avail_space);
+
+	for (num_written = 0; num_written < avail_space && ctrl->len;
+	     num_written += instance->tx_channel.stride, target += instance->tx_channel.stride) {
+		unsigned int data_len = min_t(unsigned int, skb->len, ATM_CELL_PAYLOAD);
+		unsigned int left = ATM_CELL_PAYLOAD - data_len;
+		u8 *ptr = target;
+
+		ptr[0] = ctrl->vcc->vpi >> 4;
+		ptr[1] = (ctrl->vcc->vpi << 4) | (ctrl->vcc->vci >> 12);
+		ptr[2] = ctrl->vcc->vci >> 4;
+		ptr[3] = ctrl->vcc->vci << 4;
+		ptr[4] = 0xec;
+		ptr += ATM_CELL_HEADER;
+
+		memcpy(ptr, skb->data, data_len);
+		ptr += data_len;
+		skb_pull(skb, data_len);
 
-	ctrl->num_cells = UDSL_NUM_CELLS(skb->len);
-	ctrl->num_entire = skb->len / ATM_CELL_PAYLOAD;
+		if(!left)
+			continue;
 
-	zero_padding = ctrl->num_cells * ATM_CELL_PAYLOAD - skb->len - ATM_AAL5_TRAILER;
+		memset(ptr, 0, left);
 
-	if (ctrl->num_entire + 1 < ctrl->num_cells)
-		ctrl->pdu_padding = zero_padding - (ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER);
-	else
-		ctrl->pdu_padding = zero_padding;
+		if (left >= ATM_AAL5_TRAILER) {	/* trailer will go in this cell */
+			u8 *trailer = target + ATM_CELL_SIZE - ATM_AAL5_TRAILER;
+			/* trailer[0] = 0;		UU = 0 */
+			/* trailer[1] = 0;		CPI = 0 */
+			trailer[2] = ctrl->len >> 8;
+			trailer[3] = ctrl->len;
 
-	ctrl->aal5_trailer[0] = 0;	/* UU = 0 */
-	ctrl->aal5_trailer[1] = 0;	/* CPI = 0 */
-	ctrl->aal5_trailer[2] = skb->len >> 8;
-	ctrl->aal5_trailer[3] = skb->len;
-
-	crc = crc32_be(~0, skb->data, skb->len);
-	crc = crc32_be(crc, zeros, zero_padding);
-	crc = crc32_be(crc, ctrl->aal5_trailer, 4);
-	crc = ~crc;
-
-	ctrl->aal5_trailer[4] = crc >> 24;
-	ctrl->aal5_trailer[5] = crc >> 16;
-	ctrl->aal5_trailer[6] = crc >> 8;
-	ctrl->aal5_trailer[7] = crc;
-}
-
-static unsigned int usbatm_write_cells(struct usbatm_data *instance,
-				     unsigned int howmany, struct sk_buff *skb,
-				     unsigned char **target_p)
-{
-	struct usbatm_control *ctrl = UDSL_SKB(skb);
-	unsigned char *target = *target_p;
-	unsigned int nc, ne, i;
+			ctrl->crc = ~ crc32_be(ctrl->crc, ptr, left - 4);
 
-	vdbg("usbatm_write_cells: howmany=%u, skb->len=%d, num_cells=%u, num_entire=%u, pdu_padding=%u", howmany, skb->len, ctrl->num_cells, ctrl->num_entire, ctrl->pdu_padding);
+			trailer[4] = ctrl->crc >> 24;
+			trailer[5] = ctrl->crc >> 16;
+			trailer[6] = ctrl->crc >> 8;
+			trailer[7] = ctrl->crc;
 
-	nc = ctrl->num_cells;
-	ne = min(howmany, ctrl->num_entire);
+			target[3] |= 0x2;	/* adjust PTI */
 
-	for (i = 0; i < ne; i++) {
-		usbatm_fill_cell_header(target, ctrl->atm_data.vcc);
-		target += ATM_CELL_HEADER;
-		memcpy(target, skb->data, ATM_CELL_PAYLOAD);
-		target += ATM_CELL_PAYLOAD;
-		if (instance->tx_padding) {
-			memset(target, 0, instance->tx_padding);
-			target += instance->tx_padding;
+			ctrl->len = 0;		/* tag this skb finished */
 		}
-		__skb_pull(skb, ATM_CELL_PAYLOAD);
+		else
+			ctrl->crc = crc32_be(ctrl->crc, ptr, left);
 	}
 
-	ctrl->num_entire -= ne;
-
-	if (!(ctrl->num_cells -= ne) || !(howmany -= ne))
-		goto out;
-
-	usbatm_fill_cell_header(target, ctrl->atm_data.vcc);
-	target += ATM_CELL_HEADER;
-	memcpy(target, skb->data, skb->len);
-	target += skb->len;
-	__skb_pull(skb, skb->len);
-	memset(target, 0, ctrl->pdu_padding);
-	target += ctrl->pdu_padding;
-
-	if (--ctrl->num_cells) {
-		if (!--howmany) {
-			ctrl->pdu_padding = ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER;
-			goto out;
-		}
-
-		if (instance->tx_padding) {
-			memset(target, 0, instance->tx_padding);
-			target += instance->tx_padding;
-		}
-		usbatm_fill_cell_header(target, ctrl->atm_data.vcc);
-		target += ATM_CELL_HEADER;
-		memset(target, 0, ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER);
-		target += ATM_CELL_PAYLOAD - ATM_AAL5_TRAILER;
-
-		--ctrl->num_cells;
-		UDSL_ASSERT(!ctrl->num_cells);
-	}
-
-	memcpy(target, ctrl->aal5_trailer, ATM_AAL5_TRAILER);
-	target += ATM_AAL5_TRAILER;
-	/* set pti bit in last cell */
-	*(target + 3 - ATM_CELL_SIZE) |= 0x2;
-	if (instance->tx_padding) {
-		memset(target, 0, instance->tx_padding);
-		target += instance->tx_padding;
-	}
- out:
-	*target_p = target;
-	return nc - ctrl->num_cells;
+	return num_written;
 }
 
 /**************
 **  receive  **
 **************/
 
-static void usbatm_complete_receive(struct urb *urb, struct pt_regs *regs)
+static void usbatm_rx_process(unsigned long data)
 {
-	struct usbatm_receive_buffer *buf;
-	struct usbatm_data *instance;
-	struct usbatm_receiver *rcv;
-	unsigned long flags;
-
-	if (!urb || !(rcv = urb->context)) {
-		dbg("usbatm_complete_receive: bad urb!");
-		return;
-	}
-
-	instance = rcv->instance;
-	buf = rcv->buffer;
-
-	buf->filled_cells = urb->actual_length / (ATM_CELL_SIZE + instance->rx_padding);
-
-	vdbg("usbatm_complete_receive: urb 0x%p, status %d, actual_length %d, filled_cells %u, rcv 0x%p, buf 0x%p", urb, urb->status, urb->actual_length, buf->filled_cells, rcv, buf);
+	struct usbatm_data *instance = (struct usbatm_data *)data;
+	struct usbatm_transceiver *rx;
 
-	UDSL_ASSERT(buf->filled_cells <= rcv_buf_size);
+	while ((rx = usbatm_pop_transceiver(&instance->rx_channel))) {
+		vdbg("usbatm_rx_process: processing rx 0x%p", rx);
 
-	/* may not be in_interrupt() */
-	spin_lock_irqsave(&instance->receive_lock, flags);
-	list_add(&rcv->list, &instance->spare_receivers);
-	list_add_tail(&buf->list, &instance->filled_receive_buffers);
-	if (likely(!urb->status))
-		tasklet_schedule(&instance->receive_tasklet);
-	spin_unlock_irqrestore(&instance->receive_lock, flags);
-}
+		if (!rx->urb->status)
+			usbatm_extract_cells(instance, rx->urb->transfer_buffer,
+					     rx->urb->actual_length);
 
-static void usbatm_process_receive(unsigned long data)
-{
-	struct usbatm_receive_buffer *buf;
-	struct usbatm_data *instance = (struct usbatm_data *)data;
-	struct usbatm_receiver *rcv;
-	int err;
-
- made_progress:
-	while (!list_empty(&instance->spare_receive_buffers)) {
-		spin_lock_irq(&instance->receive_lock);
-		if (list_empty(&instance->spare_receivers)) {
-			spin_unlock_irq(&instance->receive_lock);
-			break;
-		}
-		rcv = list_entry(instance->spare_receivers.next,
-				 struct usbatm_receiver, list);
-		list_del(&rcv->list);
-		spin_unlock_irq(&instance->receive_lock);
-
-		buf = list_entry(instance->spare_receive_buffers.next,
-				 struct usbatm_receive_buffer, list);
-		list_del(&buf->list);
-
-		rcv->buffer = buf;
-
-		usb_fill_bulk_urb(rcv->urb, instance->usb_dev,
-				  instance->rx_endpoint,
-				  buf->base,
-				  rcv_buf_size * (ATM_CELL_SIZE + instance->rx_padding),
-				  usbatm_complete_receive, rcv);
-
-		vdbg("usbatm_process_receive: sending urb 0x%p, rcv 0x%p, buf 0x%p",
-		     rcv->urb, rcv, buf);
-
-		if ((err = usb_submit_urb(rcv->urb, GFP_ATOMIC)) < 0) {
-			dbg("usbatm_process_receive: urb submission failed (%d)!", err);
-			list_add(&buf->list, &instance->spare_receive_buffers);
-			spin_lock_irq(&instance->receive_lock);
-			list_add(&rcv->list, &instance->spare_receivers);
-			spin_unlock_irq(&instance->receive_lock);
-			break;
-		}
+		if (usbatm_submit(rx))
+			return;
 	}
-
-	spin_lock_irq(&instance->receive_lock);
-	if (list_empty(&instance->filled_receive_buffers)) {
-		spin_unlock_irq(&instance->receive_lock);
-		return;		/* done - no more buffers */
-	}
-	buf = list_entry(instance->filled_receive_buffers.next,
-			 struct usbatm_receive_buffer, list);
-	list_del(&buf->list);
-	spin_unlock_irq(&instance->receive_lock);
-
-	vdbg("usbatm_process_receive: processing buf 0x%p", buf);
-	usbatm_extract_cells(instance, buf->base, buf->filled_cells);
-	list_add(&buf->list, &instance->spare_receive_buffers);
-	goto made_progress;
 }
 
 /***********
 **  send  **
 ***********/
 
-static void usbatm_complete_send(struct urb *urb, struct pt_regs *regs)
-{
-	struct usbatm_data *instance;
-	struct usbatm_sender *snd;
-	unsigned long flags;
-
-	if (!urb || !(snd = urb->context) || !(instance = snd->instance)) {
-		dbg("usbatm_complete_send: bad urb!");
-		return;
-	}
-
-	vdbg("usbatm_complete_send: urb 0x%p, status %d, snd 0x%p, buf 0x%p", urb,
-	     urb->status, snd, snd->buffer);
-
-	/* may not be in_interrupt() */
-	spin_lock_irqsave(&instance->send_lock, flags);
-	list_add(&snd->list, &instance->spare_senders);
-	list_add(&snd->buffer->list, &instance->spare_send_buffers);
-	tasklet_schedule(&instance->send_tasklet);
-	spin_unlock_irqrestore(&instance->send_lock, flags);
-}
-
-static void usbatm_process_send(unsigned long data)
+static void usbatm_tx_process(unsigned long data)
 {
-	struct usbatm_send_buffer *buf;
 	struct usbatm_data *instance = (struct usbatm_data *)data;
-	struct sk_buff *skb;
-	struct usbatm_sender *snd;
-	int err;
-	unsigned int num_written;
-
- made_progress:
-	spin_lock_irq(&instance->send_lock);
-	while (!list_empty(&instance->spare_senders)) {
-		if (!list_empty(&instance->filled_send_buffers)) {
-			buf = list_entry(instance->filled_send_buffers.next,
-					 struct usbatm_send_buffer, list);
-			list_del(&buf->list);
-		} else if ((buf = instance->current_buffer)) {
-			instance->current_buffer = NULL;
-		} else		/* all buffers empty */
-			break;
-
-		snd = list_entry(instance->spare_senders.next,
-				 struct usbatm_sender, list);
-		list_del(&snd->list);
-		spin_unlock_irq(&instance->send_lock);
-
-		snd->buffer = buf;
-		usb_fill_bulk_urb(snd->urb, instance->usb_dev,
-				  instance->tx_endpoint,
-				  buf->base,
-				  (snd_buf_size - buf->free_cells) * (ATM_CELL_SIZE + instance->tx_padding),
-				  usbatm_complete_send, snd);
-
-		vdbg("usbatm_process_send: submitting urb 0x%p (%d cells), snd 0x%p, buf 0x%p",
-		     snd->urb, snd_buf_size - buf->free_cells, snd, buf);
-
-		if ((err = usb_submit_urb(snd->urb, GFP_ATOMIC)) < 0) {
-			dbg("usbatm_process_send: urb submission failed (%d)!", err);
-			spin_lock_irq(&instance->send_lock);
-			list_add(&snd->list, &instance->spare_senders);
-			spin_unlock_irq(&instance->send_lock);
-			list_add(&buf->list, &instance->filled_send_buffers);
-			return;	/* bail out */
+	struct sk_buff *skb = instance->current_skb;
+	struct usbatm_transceiver *tx = NULL;
+	unsigned int num_written = 0;
+	const unsigned int buf_size = instance->tx_channel.buf_size;
+	u8 *buffer = NULL;
+
+	if (!skb)
+		skb = skb_dequeue(&instance->sndqueue);
+
+	while (skb) {
+		if (!tx) {
+			tx = usbatm_pop_transceiver(&instance->tx_channel);
+			if (!tx)
+				break;		/* no more senders */
+			buffer = tx->urb->transfer_buffer;
+			num_written = 0;
 		}
 
-		spin_lock_irq(&instance->send_lock);
-	}			/* while */
-	spin_unlock_irq(&instance->send_lock);
-
-	if (!instance->current_skb)
-		instance->current_skb = skb_dequeue(&instance->sndqueue);
-	if (!instance->current_skb)
-		return;		/* done - no more skbs */
-
-	skb = instance->current_skb;
-
-	if (!(buf = instance->current_buffer)) {
-		spin_lock_irq(&instance->send_lock);
-		if (list_empty(&instance->spare_send_buffers)) {
-			instance->current_buffer = NULL;
-			spin_unlock_irq(&instance->send_lock);
-			return;	/* done - no more buffers */
-		}
-		buf = list_entry(instance->spare_send_buffers.next,
-			       struct usbatm_send_buffer, list);
-		list_del(&buf->list);
-		spin_unlock_irq(&instance->send_lock);
-
-		buf->free_start = buf->base;
-		buf->free_cells = snd_buf_size;
+		num_written += usbatm_write_cells(instance, skb,
+						  buffer + num_written,
+						  buf_size - num_written);
 
-		instance->current_buffer = buf;
-	}
+		vdbg("usbatm_tx_process: wrote %u bytes from skb 0x%p to sender 0x%p",
+		     num_written, skb, tx);
 
-	num_written = usbatm_write_cells(instance, buf->free_cells, skb, &buf->free_start);
+		if (!UDSL_SKB(skb)->len) {
+			struct atm_vcc *vcc = UDSL_SKB(skb)->vcc;
 
-	vdbg("usbatm_process_send: wrote %u cells from skb 0x%p to buffer 0x%p",
-	     num_written, skb, buf);
-
-	if (!(buf->free_cells -= num_written)) {
-		list_add_tail(&buf->list, &instance->filled_send_buffers);
-		instance->current_buffer = NULL;
-	}
+			usbatm_pop(vcc, skb);
+			atomic_inc(&vcc->stats->tx);
 
-	vdbg("usbatm_process_send: buffer contains %d cells, %d left",
-	     snd_buf_size - buf->free_cells, buf->free_cells);
+			skb = skb_dequeue(&instance->sndqueue);
+		}
 
-	if (!UDSL_SKB(skb)->num_cells) {
-		struct atm_vcc *vcc = UDSL_SKB(skb)->atm_data.vcc;
+		if (num_written == buf_size || !skb) {
+			tx->urb->transfer_buffer_length = num_written;
 
-		usbatm_pop(vcc, skb);
-		instance->current_skb = NULL;
+			if (usbatm_submit(tx))
+				break;
+			tx = NULL;
+		}
 
-		atomic_inc(&vcc->stats->tx);
 	}
 
-	goto made_progress;
+	instance->current_skb = skb;
 }
 
 static void usbatm_cancel_send(struct usbatm_data *instance,
-			     struct atm_vcc *vcc)
+			       struct atm_vcc *vcc)
 {
 	struct sk_buff *skb, *n;
 
@@ -682,26 +627,27 @@
 	for (skb = instance->sndqueue.next, n = skb->next;
 	     skb != (struct sk_buff *)&instance->sndqueue;
 	     skb = n, n = skb->next)
-		if (UDSL_SKB(skb)->atm_data.vcc == vcc) {
+		if (UDSL_SKB(skb)->vcc == vcc) {
 			dbg("usbatm_cancel_send: popping skb 0x%p", skb);
 			__skb_unlink(skb, &instance->sndqueue);
 			usbatm_pop(vcc, skb);
 		}
 	spin_unlock_irq(&instance->sndqueue.lock);
 
-	tasklet_disable(&instance->send_tasklet);
-	if ((skb = instance->current_skb) && (UDSL_SKB(skb)->atm_data.vcc == vcc)) {
+	tasklet_disable(&instance->tx_channel.tasklet);
+	if ((skb = instance->current_skb) && (UDSL_SKB(skb)->vcc == vcc)) {
 		dbg("usbatm_cancel_send: popping current skb (0x%p)", skb);
 		instance->current_skb = NULL;
 		usbatm_pop(vcc, skb);
 	}
-	tasklet_enable(&instance->send_tasklet);
+	tasklet_enable(&instance->tx_channel.tasklet);
 	dbg("usbatm_cancel_send done");
 }
 
 static int usbatm_atm_send(struct atm_vcc *vcc, struct sk_buff *skb)
 {
 	struct usbatm_data *instance = vcc->dev->dev_data;
+	struct usbatm_control *ctrl = UDSL_SKB(skb);
 	int err;
 
 	vdbg("usbatm_atm_send called (skb 0x%p, len %u)", skb, skb->len);
@@ -727,9 +673,13 @@
 
 	PACKETDEBUG(skb->data, skb->len);
 
-	usbatm_groom_skb(vcc, skb);
+	/* initialize the control block */
+	ctrl->vcc = vcc;
+	ctrl->len = skb->len;
+	ctrl->crc = crc32_be(~0, skb->data, skb->len);
+
 	skb_queue_tail(&instance->sndqueue, skb);
-	tasklet_schedule(&instance->send_tasklet);
+	tasklet_schedule(&instance->tx_channel.tasklet);
 
 	return 0;
 
@@ -749,8 +699,8 @@
 
 	dbg("usbatm_destroy_instance");
 
-	tasklet_kill(&instance->receive_tasklet);
-	tasklet_kill(&instance->send_tasklet);
+	tasklet_kill(&instance->rx_channel.tasklet);
+	tasklet_kill(&instance->tx_channel.tasklet);
 	usb_put_dev(instance->usb_dev);
 	kfree(instance);
 }
@@ -832,7 +782,6 @@
 {
 	struct usbatm_data *instance = vcc->dev->dev_data;
 	struct usbatm_vcc_data *new;
-	unsigned int max_pdu;
 	int vci = vcc->vci;
 	short vpi = vcc->vpi;
 
@@ -869,9 +818,8 @@
 	new->vpi = vpi;
 	new->vci = vci;
 
-	/* usbatm_extract_cells requires at least one cell */
-	max_pdu = max(1, UDSL_NUM_CELLS(vcc->qos.rxtp.max_sdu)) * ATM_CELL_PAYLOAD;
-	if (!(new->sarb = alloc_skb(max_pdu, GFP_KERNEL))) {
+	new->sarb = alloc_skb(usbatm_pdu_length(vcc->qos.rxtp.max_sdu), GFP_KERNEL);
+	if (!new->sarb) {
 		dbg("usbatm_atm_open: no memory for SAR buffer!");
 		kfree(new);
 		up(&instance->serialize);
@@ -880,9 +828,9 @@
 
 	vcc->dev_data = new;
 
-	tasklet_disable(&instance->receive_tasklet);
+	tasklet_disable(&instance->rx_channel.tasklet);
 	list_add(&new->list, &instance->vcc_list);
-	tasklet_enable(&instance->receive_tasklet);
+	tasklet_enable(&instance->rx_channel.tasklet);
 
 	set_bit(ATM_VF_ADDR, &vcc->flags);
 	set_bit(ATM_VF_PARTIAL, &vcc->flags);
@@ -890,9 +838,7 @@
 
 	up(&instance->serialize);
 
-	tasklet_schedule(&instance->receive_tasklet);
-
-	dbg("usbatm_atm_open: allocated vcc data 0x%p (max_pdu: %u)", new, max_pdu);
+	dbg("usbatm_atm_open: allocated vcc data 0x%p", new);
 
 	return 0;
 }
@@ -916,9 +862,9 @@
 
 	down(&instance->serialize);	/* vs self, usbatm_atm_open */
 
-	tasklet_disable(&instance->receive_tasklet);
+	tasklet_disable(&instance->rx_channel.tasklet);
 	list_del(&vcc_data->list);
-	tasklet_enable(&instance->receive_tasklet);
+	tasklet_enable(&instance->rx_channel.tasklet);
 
 	kfree_skb(vcc_data->sarb);
 	vcc_data->sarb = NULL;
@@ -951,7 +897,7 @@
 static int usbatm_atm_init(struct usbatm_data *instance)
 {
 	struct atm_dev *atm_dev;
-	int ret;
+	int ret, i;
 
 	/* ATM init */
 	atm_dev = atm_dev_register(instance->driver_name, &usbatm_atm_devops, -1, NULL);
@@ -979,6 +925,10 @@
 	mb();
 	atm_dev->dev_data = instance;
 
+	/* submit all rx URBs */
+	for (i = 0; i < num_rcv_urbs; i++)
+		usbatm_submit(instance->transceivers + i);
+
 	return 0;
 
  fail:
@@ -1032,8 +982,32 @@
 	return 0;
 }
 
-int usbatm_usb_probe (struct usb_interface *intf, const struct usb_device_id *id,
-		struct usbatm_driver *driver)
+static void usbatm_tasklet_schedule(unsigned long data)
+{
+        tasklet_schedule((struct tasklet *) data);
+}
+
+static void usbatm_clear_halt(void *data)
+{
+	struct usbatm_channel *channel = data;
+
+	/* the processing will get restarted after throttling delay */
+	usb_clear_halt(channel->usbatm->usb_dev, channel->endpoint);
+}
+
+static inline void usbatm_init_channel(struct usbatm_channel *channel)
+{
+	spin_lock_init(&channel->lock);
+	INIT_LIST_HEAD(&channel->list);
+	channel->delay.function = usbatm_tasklet_schedule;
+	channel->delay.data = (unsigned long) &channel->tasklet;
+	init_timer(&channel->delay);
+	INIT_WORK(&channel->clear_halt_work, usbatm_clear_halt, channel);
+	atomic_set(&channel->num_allowed_errors, USBATM_MAX_ERRORS);
+}
+
+int usbatm_usb_probe(struct usb_interface *intf, const struct usb_device_id *id,
+		     struct usbatm_driver *driver)
 {
 	struct usb_device *dev = interface_to_usbdev(intf);
 	struct usbatm_data *instance;
@@ -1063,10 +1037,12 @@
 
 	instance->usb_dev = dev;
 	instance->usb_intf = intf;
-	instance->tx_endpoint = usb_sndbulkpipe(dev, driver->out);
-	instance->rx_endpoint = usb_rcvbulkpipe(dev, driver->in);
-	instance->tx_padding = driver->tx_padding;
-	instance->rx_padding = driver->rx_padding;
+	instance->rx_channel.endpoint = usb_rcvbulkpipe(dev, driver->in);
+	instance->tx_channel.endpoint = usb_sndbulkpipe(dev, driver->out);
+	instance->rx_channel.stride = ATM_CELL_SIZE + driver->rx_padding;
+	instance->tx_channel.stride = ATM_CELL_SIZE + driver->tx_padding;
+	instance->rx_channel.buf_size = rcv_buf_size * instance->rx_channel.stride;
+	instance->tx_channel.buf_size = snd_buf_size * instance->tx_channel.stride;
 
 	buf = instance->description;
 	length = sizeof(instance->description);
@@ -1107,74 +1083,50 @@
 
 	INIT_LIST_HEAD(&instance->vcc_list);
 
-	spin_lock_init(&instance->receive_lock);
-	INIT_LIST_HEAD(&instance->spare_receivers);
-	INIT_LIST_HEAD(&instance->filled_receive_buffers);
-
-	tasklet_init(&instance->receive_tasklet, usbatm_process_receive, (unsigned long)instance);
-	INIT_LIST_HEAD(&instance->spare_receive_buffers);
+	tasklet_init(&instance->rx_channel.tasklet, usbatm_rx_process, (unsigned long)instance);
+	tasklet_init(&instance->tx_channel.tasklet, usbatm_tx_process, (unsigned long)instance);
+	usbatm_init_channel(&instance->rx_channel);
+	usbatm_init_channel(&instance->tx_channel);
+	instance->rx_channel.usbatm = instance->tx_channel.usbatm = instance;
 
 	skb_queue_head_init(&instance->sndqueue);
 
-	spin_lock_init(&instance->send_lock);
-	INIT_LIST_HEAD(&instance->spare_senders);
-	INIT_LIST_HEAD(&instance->spare_send_buffers);
-
-	tasklet_init(&instance->send_tasklet, usbatm_process_send, (unsigned long)instance);
-	INIT_LIST_HEAD(&instance->filled_send_buffers);
-
-	/* receive init */
-	for (i = 0; i < num_rcv_urbs; i++) {
-		struct usbatm_receiver *rcv = &(instance->receivers[i]);
-
-		if (!(rcv->urb = usb_alloc_urb(0, GFP_KERNEL))) {
-			dev_dbg(&intf->dev, "no memory for receive urb %d!\n", i);
-			goto fail_unbind;
-		}
-
-		rcv->instance = instance;
-
-		list_add(&rcv->list, &instance->spare_receivers);
+	instance->transceivers = kmalloc(sizeof(*instance->transceivers) * (num_rcv_urbs + num_snd_urbs),
+					 GFP_KERNEL);
+	if (!instance->transceivers) {
+		dev_dbg(&intf->dev, "no memory for receive urb %d!\n", i);
+		goto fail_unbind;
 	}
 
-	for (i = 0; i < num_rcv_bufs; i++) {
-		struct usbatm_receive_buffer *buf = &(instance->receive_buffers[i]);
+	memset(instance->transceivers, 0, sizeof(*instance->transceivers) * (num_rcv_urbs + num_snd_urbs));
 
-		buf->base = kmalloc(rcv_buf_size * (ATM_CELL_SIZE + instance->rx_padding),
-				    GFP_KERNEL);
-		if (!buf->base) {
-			dev_dbg(&intf->dev, "no memory for receive buffer %d!\n", i);
+	for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) {
+		struct usbatm_transceiver *trx = instance->transceivers + i;
+		u8 *buffer;
+		trx->channel = i < num_rcv_urbs ? &instance->rx_channel : &instance->tx_channel;
+
+		buffer = kmalloc(trx->channel->buf_size, GFP_KERNEL);
+		if (!buffer) {
+			dev_dbg(&intf->dev, "no memory for buffer %d!\n", i);
 			goto fail_unbind;
 		}
+		memset(buffer, 0, trx->channel->buf_size);
 
-		list_add(&buf->list, &instance->spare_receive_buffers);
-	}
-
-	/* send init */
-	for (i = 0; i < num_snd_urbs; i++) {
-		struct usbatm_sender *snd = &(instance->senders[i]);
-
-		if (!(snd->urb = usb_alloc_urb(0, GFP_KERNEL))) {
-			dev_dbg(&intf->dev, "usbatm_usb_probe: no memory for send urb %d!\n", i);
+		trx->urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!trx->urb) {
+			dev_dbg(&intf->dev, "no memory for urb %d!\n", i);
 			goto fail_unbind;
 		}
 
-		snd->instance = instance;
-
-		list_add(&snd->list, &instance->spare_senders);
-	}
+		usb_fill_bulk_urb(trx->urb, instance->usb_dev, trx->channel->endpoint,
+				  buffer, trx->channel->buf_size, usbatm_complete, trx);
 
-	for (i = 0; i < num_snd_bufs; i++) {
-		struct usbatm_send_buffer *buf = &(instance->send_buffers[i]);
+		/* put all tx URBs on the list of spares */
+		if (i >= num_rcv_urbs)
+			list_add(&trx->list, &trx->channel->list);
 
-		buf->base = kmalloc(snd_buf_size * (ATM_CELL_SIZE + instance->tx_padding),
-				    GFP_KERNEL);
-		if (!buf->base) {
-			dev_dbg(&intf->dev, "no memory for send buffer %d!\n", i);
-			goto fail_unbind;
-		}
-
-		list_add(&buf->list, &instance->spare_send_buffers);
+		vdbg("alloced trx 0x%p buffer 0x%p buf size %u urb 0x%p",
+		     trx, trx->urb->transfer_buffer, trx->urb->transfer_buffer_length, trx->urb);
 	}
 
 	if (need_heavy && driver->heavy_init) {
@@ -1196,18 +1148,13 @@
 	if (instance->driver->unbind)
 		instance->driver->unbind(instance, intf);
  fail_free:
-	for (i = 0; i < num_snd_bufs; i++)
-		kfree(instance->send_buffers[i].base);
-
-	for (i = 0; i < num_snd_urbs; i++)
-		usb_free_urb(instance->senders[i].urb);
-
-	for (i = 0; i < num_rcv_bufs; i++)
-		kfree(instance->receive_buffers[i].base);
-
-	for (i = 0; i < num_rcv_urbs; i++)
-		usb_free_urb(instance->receivers[i].urb);
+	if (instance->transceivers)
+		for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) {
+			kfree(instance->transceivers[i].urb->transfer_buffer);
+			usb_free_urb(instance->transceivers[i].urb);
+		}
 
+	kfree(instance->transceivers);
 	kfree (instance);
 
 	return error;
@@ -1235,49 +1182,37 @@
 
 	wait_for_completion(&instance->thread_exited);
 
-	tasklet_disable(&instance->receive_tasklet);
-	tasklet_disable(&instance->send_tasklet);
+	tasklet_disable(&instance->rx_channel.tasklet);
+	tasklet_disable(&instance->tx_channel.tasklet);
+
+	del_timer_sync(&instance->rx_channel.delay);
+	del_timer_sync(&instance->tx_channel.delay);
 
 	if (instance->atm_dev && instance->driver->atm_stop)
 		instance->driver->atm_stop(instance, instance->atm_dev);
 
-	if (instance->driver->unbind)
-		instance->driver->unbind(instance, intf);
+	for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++)
+		usb_kill_urb(instance->transceivers[i].urb);
 
-	/* receive finalize */
-
-	for (i = 0; i < num_rcv_urbs; i++)
-		usb_kill_urb(instance->receivers[i].urb);
+	flush_scheduled_work();		/* in case we scheduled clear_halt */
 
-	/* no need to take the spinlock */
-	INIT_LIST_HEAD(&instance->filled_receive_buffers);
-	INIT_LIST_HEAD(&instance->spare_receive_buffers);
-
-	tasklet_enable(&instance->receive_tasklet);
-
-	for (i = 0; i < num_rcv_urbs; i++)
-		usb_free_urb(instance->receivers[i].urb);
-
-	for (i = 0; i < num_rcv_bufs; i++)
-		kfree(instance->receive_buffers[i].base);
-
-	/* send finalize */
-
-	for (i = 0; i < num_snd_urbs; i++)
-		usb_kill_urb(instance->senders[i].urb);
+	if (instance->driver->unbind)
+		instance->driver->unbind(instance, intf);
 
+	/* turn usbatm_[rt]x_process into noop */
 	/* no need to take the spinlock */
-	INIT_LIST_HEAD(&instance->spare_senders);
-	INIT_LIST_HEAD(&instance->spare_send_buffers);
-	instance->current_buffer = NULL;
+	INIT_LIST_HEAD(&instance->rx_channel.list);
+	INIT_LIST_HEAD(&instance->tx_channel.list);
 
-	tasklet_enable(&instance->send_tasklet);
+	tasklet_enable(&instance->rx_channel.tasklet);
+	tasklet_enable(&instance->tx_channel.tasklet);
 
-	for (i = 0; i < num_snd_urbs; i++)
-		usb_free_urb(instance->senders[i].urb);
+	for (i = 0; i < num_rcv_urbs + num_snd_urbs; i++) {
+		kfree(instance->transceivers[i].urb->transfer_buffer);
+		usb_free_urb(instance->transceivers[i].urb);
+	}
 
-	for (i = 0; i < num_snd_bufs; i++)
-		kfree(instance->send_buffers[i].base);
+	kfree(instance->transceivers);
 
 	/* ATM finalize */
 	if (instance->atm_dev)
@@ -1303,8 +1238,6 @@
 
 	if ((num_rcv_urbs > UDSL_MAX_RCV_URBS)
 	    || (num_snd_urbs > UDSL_MAX_SND_URBS)
-	    || (num_rcv_bufs > UDSL_MAX_RCV_BUFS)
-	    || (num_snd_bufs > UDSL_MAX_SND_BUFS)
 	    || (rcv_buf_size > UDSL_MAX_RCV_BUF_SIZE)
 	    || (snd_buf_size > UDSL_MAX_SND_BUF_SIZE))
 		return -EINVAL;



More information about the Usbatm mailing list