patch-2.4.0-test2 linux/drivers/usb/serial/digi_acceleport.c

Next file: linux/drivers/usb/serial/usbserial.c
Previous file: linux/drivers/usb/scanner.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test1/linux/drivers/usb/serial/digi_acceleport.c linux/drivers/usb/serial/digi_acceleport.c
@@ -14,24 +14,75 @@
 *  Peter Berger (pberger@brimson.com)
 *  Al Borchers (borchers@steinerpoint.com)
 *
+*  (6/4/2000) pberger and borchers
+*    -- Replaced separate calls to spin_unlock_irqrestore and
+*       interruptible_sleep_on_interruptible with a new function
+*       cond_wait_interruptible_timeout_irqrestore.  This eliminates
+*       the race condition where the wake up could happen after
+*       the unlock and before the sleep.
+*    -- Close now waits for output to drain.
+*    -- Open waits until any close in progress is finished.
+*    -- All out of band responses are now processed, not just the
+*       first in a USB packet.
+*    -- Fixed a bug that prevented the driver from working when the
+*       first Digi port was not the first USB serial port--the driver
+*       was mistakenly using the external USB serial port number to
+*       try to index into its internal ports.
+*    -- Fixed an SMP bug -- write_bulk_callback is called directly from
+*       an interrupt, so spin_lock_irqsave/spin_unlock_irqrestore are
+*       needed for locks outside write_bulk_callback that are also
+*       acquired by write_bulk_callback to prevent deadlocks.
+*    -- Fixed support for select() by making digi_chars_in_buffer()
+*       return 256 when -EINPROGRESS is set, as the line discipline
+*       code in n_tty.c expects.
+*    -- Fixed an include file ordering problem that prevented debugging
+*       messages from working.
+*    -- Fixed an intermittent timeout problem that caused writes to
+*       sometimes get stuck on some machines on some kernels.  It turns
+*       out in these circumstances write_chan() (in n_tty.c) was
+*       asleep waiting for our wakeup call.  Even though we call
+*       wake_up_interruptible() in digi_write_bulk_callback(), there is
+*       a race condition that could cause the wakeup to fail: if our
+*       wake_up_interruptible() call occurs between the time that our
+*       driver write routine finishes and write_chan() sets current->state
+*       to TASK_INTERRUPTIBLE, the effect of our wakeup setting the state
+*       to TASK_RUNNING will be lost and write_chan's subsequent call to
+*       schedule() will never return (unless it catches a signal).
+*       This race condition occurs because write_bulk_callback() (and thus
+*       the wakeup) are called asynchonously from an interrupt, rather than
+*       from the scheduler.  We can avoid the race by calling the wakeup
+*       from the scheduler queue and that's our fix:  Now, at the end of
+*       write_bulk_callback() we queue up a wakeup call on the scheduler
+*       task queue.  We still also invoke the wakeup directly since that
+*       squeezes a bit more performance out of the driver, and any lost
+*       race conditions will get cleaned up at the next scheduler run.
+*
+*       NOTE:  The problem also goes away if you comment out
+*       the two code lines in write_chan() where current->state
+*       is set to TASK_RUNNING just before calling driver.write() and to
+*       TASK_INTERRUPTIBLE immediately afterwards.  This is why the
+*       problem did not show up with the 2.2 kernels -- they do not
+*       include that code.
+*
 *  (5/16/2000) pberger and borchers
-*    -- added timeouts to sleeps
-*    -- handle transition to/from B0 in digi_set_termios
+*    -- Added timeouts to sleeps, to defend against lost wake ups.
+*    -- Handle transition to/from B0 baud rate in digi_set_termios.
 *
 *  (5/13/2000) pberger and borchers
-*    -- all commands now sent on out of band port, using digi_write_oob
-*    -- get modem control signals whenever they change, support TIOCMGET/
-*       SET/BIS/BIC ioctls
+*    -- All commands now sent on out of band port, using
+*       digi_write_oob_command.
+*    -- Get modem control signals whenever they change, support TIOCMGET/
+*       SET/BIS/BIC ioctls.
 *    -- digi_set_termios now supports parity, word size, stop bits, and
-*       receive enable
-*    -- cleaned up open and close, use digi_set_termios and digi_write_oob
-*       to set port parameters
-*    -- added digi_startup_device to start read chains on all ports
-*    -- write buffer is only used when count==1, to be sure put_char can
-*       write a char (unless the buffer is full)
+*       receive enable.
+*    -- Cleaned up open and close, use digi_set_termios and
+*       digi_write_oob_command to set port parameters.
+*    -- Added digi_startup_device to start read chains on all ports.
+*    -- Write buffer is only used when count==1, to be sure put_char can
+*       write a char (unless the buffer is full).
 *
 *  (5/10/2000) pberger and borchers
-*    -- Added MOD_INC_USE_COUNT/MOD_DEC_USE_COUNT calls
+*    -- Added MOD_INC_USE_COUNT/MOD_DEC_USE_COUNT calls on open/close.
 *    -- Fixed problem where the first incoming character is lost on
 *       port opens after the first close on that port.  Now we keep
 *       the read_urb chain open until shutdown.
@@ -43,7 +94,33 @@
 *  (5/3/2000) pberger and borchers
 *    -- First alpha version of the driver--many known limitations and bugs.
 *
-*  $Id: digi_acceleport.c,v 1.43 2000/05/17 03:21:38 root Exp root $
+*
+*  Locking and SMP
+*
+*  - Each port, including the out-of-band port, has a lock used to
+*    serialize all access to the port's private structure.
+*  - The port lock is also used to serialize all writes and access to
+*    the port's URB.
+*  - The port lock is also used for the port write_wait condition
+*    variable.  Holding the port lock will prevent a wake up on the
+*    port's write_wait; this can be used with cond_wait_... to be sure
+*    the wake up is not lost in a race when dropping the lock and
+*    sleeping waiting for the wakeup.
+*  - digi_write() does not sleep, since it is sometimes called on
+*    interrupt time.
+*  - digi_write_bulk_callback() and digi_read_bulk_callback() are
+*    called directly from interrupts.  Hence spin_lock_irqsave()
+*    and spin_lock_irqrestore() are used in the rest of the code
+*    for any locks they acquire.
+*  - digi_write_bulk_callback() gets the port lock before waking up
+*    processes sleeping on the port write_wait.  It also schedules
+*    wake ups so they happen from the scheduler, because the tty
+*    system can miss wake ups from interrupts.
+*  - All sleeps use a timeout of DIGI_RETRY_TIMEOUT before looping to
+*    recheck the condition they are sleeping on.  This is defensive,
+*    in case a wake up is lost.
+*    
+*  $Id: digi_acceleport.c,v 1.56 2000/06/07 22:47:30 root Exp root $
 */
 
 #include <linux/config.h>
@@ -60,8 +137,7 @@
 #include <linux/tty.h>
 #include <linux/module.h>
 #include <linux/spinlock.h>
-#include <linux/usb.h>
-#include "usb-serial.h"
+#include <linux/tqueue.h>
 
 #ifdef CONFIG_USB_SERIAL_DEBUG
 	#define DEBUG
@@ -69,16 +145,25 @@
 	#undef DEBUG
 #endif
 
+#include <linux/usb.h>
+#include "usb-serial.h"
+
 
 /* Defines */
 
 /* port buffer length -- must be <= transfer buffer length - 2 */
 /* so we can be sure to send the full buffer in one urb */
-#define DIGI_PORT_BUF_LEN		16
+#define DIGI_PORT_BUF_LEN		8
 
-/* retry timeout while waiting for urb->status to go to 0 */
+/* retry timeout while sleeping */
 #define DIGI_RETRY_TIMEOUT		(HZ/10)
 
+/* timeout while waiting for tty output to drain in close */
+/* this delay is used twice in close, so the total delay could */
+/* be twice this value */
+#define DIGI_CLOSE_TIMEOUT		(5*HZ)
+
+
 /* AccelePort USB Defines */
 
 /* ids */
@@ -173,6 +258,9 @@
 #define DIGI_FLUSH_RX				2
 #define DIGI_RESUME_TX				4 /* clears xoff condition */
 
+#define DIGI_TRANSMIT_NOT_IDLE			0
+#define DIGI_TRANSMIT_IDLE			1
+
 #define DIGI_DISABLE				0
 #define DIGI_ENABLE				1
 
@@ -211,18 +299,29 @@
 /* Structures */
 
 typedef struct digi_private {
+	int dp_port_num;
 	spinlock_t dp_port_lock;
 	int dp_buf_len;
 	unsigned char dp_buf[DIGI_PORT_BUF_LEN];
 	unsigned int dp_modem_signals;
+	int dp_transmit_idle;
+	int dp_in_close;
+	wait_queue_head_t dp_close_wait;	/* wait queue for close */
+	struct tq_struct dp_tasks;
 } digi_private_t;
 
 
 /* Local Function Declarations */
 
-static int digi_write_oob( unsigned char *buf, int count );
+static void digi_wakeup_write( struct usb_serial_port *port );
+static void digi_wakeup_write_lock( struct usb_serial_port *port );
+static int digi_write_oob_command( unsigned char *buf, int count );
+static int digi_write_inb_command( struct usb_serial_port *port,
+	unsigned char *buf, int count ) __attribute__((unused));
 static int digi_set_modem_signals( struct usb_serial_port *port,
 	unsigned int modem_signals );
+static int digi_transmit_idle( struct usb_serial_port *port,
+	unsigned long timeout );
 static void digi_rx_throttle (struct usb_serial_port *port);
 static void digi_rx_unthrottle (struct usb_serial_port *port);
 static void digi_set_termios( struct usb_serial_port *port, 
@@ -241,22 +340,24 @@
 static int digi_startup( struct usb_serial *serial );
 static void digi_shutdown( struct usb_serial *serial );
 static void digi_read_bulk_callback( struct urb *urb );
-static void digi_read_oob( struct urb *urb );
+static void digi_read_oob_callback( struct urb *urb );
 
 
 /* Statics */
 
 /* device info needed for the Digi serial converter */
-static __u16 digi_vendor_id = DIGI_VENDOR_ID;
-static __u16 digi_product_id = DIGI_ID;
+static u16 digi_vendor_id = DIGI_VENDOR_ID;
+static u16 digi_product_id = DIGI_ID;
 
 /* out of band port */
 static int oob_port_num;			/* index of out-of-band port */
 static struct usb_serial_port *oob_port;	/* out-of-band port */
 static int device_startup = 0;
 
-/* startup lock -- used to by digi_startup_device */
-spinlock_t startup_lock;
+spinlock_t startup_lock;			/* used by startup_device */
+
+static wait_queue_head_t modem_change_wait;
+static wait_queue_head_t transmit_idle_wait;
 
 
 /* Globals */
@@ -292,7 +393,84 @@
 /* Functions */
 
 /*
-*  Digi Write OOB
+*  Cond Wait Interruptible Timeout Irqrestore
+*
+*  Do spin_unlock_irqrestore and interruptible_sleep_on_timeout
+*  so that wake ups are not lost if they occur between the unlock
+*  and the sleep.  In other words, spin_lock_irqrestore and
+*  interruptible_sleep_on_timeout are "atomic" with respect to
+*  wake ups.  This is used to implement condition variables.
+*/
+
+static long cond_wait_interruptible_timeout_irqrestore(
+	wait_queue_head_t *q, long timeout,
+	spinlock_t *lock, unsigned long flags )
+{
+
+	wait_queue_t wait;
+
+
+	init_waitqueue_entry( &wait, current );
+
+	set_current_state( TASK_INTERRUPTIBLE );
+
+	add_wait_queue( q, &wait );
+
+	spin_unlock_irqrestore( lock, flags );
+
+	timeout = schedule_timeout(timeout);
+
+	set_current_state( TASK_RUNNING );
+
+	remove_wait_queue( q, &wait );
+
+	return( timeout );
+
+}
+
+
+/*
+*  Digi Wakeup Write
+*
+*  Wake up port, line discipline, and tty processes sleeping
+*  on writes.
+*/
+
+static void digi_wakeup_write_lock( struct usb_serial_port *port )
+{
+
+	unsigned long flags;
+	digi_private_t *priv = (digi_private_t *)(port->private);
+
+
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
+	digi_wakeup_write( port );
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+
+}
+
+static void digi_wakeup_write( struct usb_serial_port *port )
+{
+
+	struct tty_struct *tty = port->tty;
+
+
+	/* wake up port processes */
+	wake_up_interruptible( &port->write_wait );
+
+	/* wake up line discipline */
+	if( (tty->flags & (1 << TTY_DO_WRITE_WAKEUP))
+	&& tty->ldisc.write_wakeup )
+		(tty->ldisc.write_wakeup)(tty);
+
+	/* wake up other tty processes */
+	wake_up_interruptible( &tty->write_wait );
+
+}
+
+
+/*
+*  Digi Write OOB Command
 *
 *  Write commands on the out of band port.  Commands are 4
 *  bytes each, multiple commands can be sent at once, and
@@ -301,28 +479,29 @@
 *  a negative error returned by usb_submit_urb.
 */
 
-static int digi_write_oob( unsigned char *buf, int count )
+static int digi_write_oob_command( unsigned char *buf, int count )
 {
 
 	int ret = 0;
 	int len;
 	digi_private_t *oob_priv = (digi_private_t *)(oob_port->private);
+	unsigned long flags = 0;
 
 
-dbg( "digi_write_oob: TOP: port=%d, count=%d", oob_port->number, count );
+dbg( "digi_write_oob_command: TOP: port=%d, count=%d", oob_port_num, count );
 
-	spin_lock( &oob_priv->dp_port_lock );
+	spin_lock_irqsave( &oob_priv->dp_port_lock, flags );
 
 	while( count > 0 ) {
 
 		while( oob_port->write_urb->status == -EINPROGRESS ) {
-			spin_unlock( &oob_priv->dp_port_lock );
-			interruptible_sleep_on_timeout( &oob_port->write_wait,
-				DIGI_RETRY_TIMEOUT );
+			cond_wait_interruptible_timeout_irqrestore(
+				&oob_port->write_wait, DIGI_RETRY_TIMEOUT,
+				&oob_priv->dp_port_lock, flags );
 			if( signal_pending(current) ) {
 				return( -EINTR );
 			}
-			spin_lock( &oob_priv->dp_port_lock );
+			spin_lock_irqsave( &oob_priv->dp_port_lock, flags );
 		}
 
 		/* len must be a multiple of 4, so commands are not split */
@@ -337,14 +516,106 @@
 			count -= len;
 			buf += len;
 		} else {
-			dbg( "digi_write_oob: usb_submit_urb failed, ret=%d",
-				ret );
+			dbg(
+			"digi_write_oob_command: usb_submit_urb failed, ret=%d",
+			ret );
+			break;
+		}
+
+	}
+
+	spin_unlock_irqrestore( &oob_priv->dp_port_lock, flags );
+
+	return( ret );
+
+}
+
+
+/*
+*  Digi Write In Band Command
+*
+*  Write commands on the given port.  Commands are 4
+*  bytes each, multiple commands can be sent at once, and
+*  no command will be split across USB packets.  Returns 0
+*  if successful, or a negative error returned by digi_write.
+*/
+
+static int digi_write_inb_command( struct usb_serial_port *port,
+	unsigned char *buf, int count )
+{
+
+	int ret = 0;
+	int len;
+	digi_private_t *priv = (digi_private_t *)(port->private);
+	unsigned char *data = port->write_urb->transfer_buffer;
+	unsigned long flags = 0;
+
+
+dbg( "digi_write_inb_command: TOP: port=%d, count=%d", priv->dp_port_num,
+count );
+
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
+
+	while( count > 0 ) {
+
+		while( port->write_urb->status == -EINPROGRESS ) {
+			cond_wait_interruptible_timeout_irqrestore(
+				&port->write_wait, DIGI_RETRY_TIMEOUT,
+				&priv->dp_port_lock, flags );
+			if( signal_pending(current) ) {
+				return( -EINTR );
+			}
+			spin_lock_irqsave( &priv->dp_port_lock, flags );
+		}
+
+		/* len must be a multiple of 4 and small enough to */
+		/* guarantee the write will send all data (or none), */
+		/* so commands are not split */
+		len = MIN( count, port->bulk_out_size-2-priv->dp_buf_len );
+		if( len > 4 )
+			len &= ~3;
+
+		/* write any buffered data first */
+		if( priv->dp_buf_len > 0 ) {
+			data[0] = DIGI_CMD_SEND_DATA;
+			data[1] = priv->dp_buf_len;
+			memcpy( data+2, priv->dp_buf, priv->dp_buf_len );
+			memcpy( data+2+priv->dp_buf_len, buf, len );
+			port->write_urb->transfer_buffer_length
+				= priv->dp_buf_len+2+len;
+		} else {
+			memcpy( data, buf, len );
+			port->write_urb->transfer_buffer_length = len;
+		}
+
+#ifdef DEBUG_DATA
+ {
+	int i;
+
+	printk( KERN_DEBUG __FILE__ ": digi_write: port=%d, length=%d, data=",
+		priv->dp_port_num, port->write_urb->transfer_buffer_length );
+	for( i=0; i<port->write_urb->transfer_buffer_length; ++i ) {
+		printk( "%.2x ",
+		((unsigned char *)port->write_urb->transfer_buffer)[i] );
+	}
+	printk( "\n" );
+ }
+#endif
+
+		if( (ret=usb_submit_urb(port->write_urb)) == 0 ) {
+			priv->dp_buf_len = 0;
+			count -= len;
+			buf += len;
+		} else {
+			dbg(
+			"digi_write_inb_command: usb_submit_urb failed, ret=%d",
+			ret );
 			break;
 		}
 
 	}
 
-	spin_unlock( &oob_priv->dp_port_lock );
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 
 	return( ret );
 
@@ -369,35 +640,35 @@
 	unsigned char *data = oob_port->write_urb->transfer_buffer;
 	digi_private_t *port_priv = (digi_private_t *)(port->private);
 	digi_private_t *oob_priv = (digi_private_t *)(oob_port->private);
+	unsigned long flags = 0;
 
 
 dbg( "digi_set_modem_signals: TOP: port=%d, modem_signals=0x%x",
-port->number, modem_signals );
+port_priv->dp_port_num, modem_signals );
 
-	spin_lock( &oob_priv->dp_port_lock );
-	spin_lock( &port_priv->dp_port_lock );
+	spin_lock_irqsave( &oob_priv->dp_port_lock, flags );
+	spin_lock_irqsave( &port_priv->dp_port_lock, flags );
 
 	while( oob_port->write_urb->status == -EINPROGRESS ) {
-		spin_unlock( &port_priv->dp_port_lock );
-		spin_unlock( &oob_priv->dp_port_lock );
-		interruptible_sleep_on_timeout( &oob_port->write_wait,
-			DIGI_RETRY_TIMEOUT );
+		spin_unlock_irqrestore( &port_priv->dp_port_lock, flags );
+		cond_wait_interruptible_timeout_irqrestore(
+			&oob_port->write_wait, DIGI_RETRY_TIMEOUT,
+			&oob_priv->dp_port_lock, flags );
 		if( signal_pending(current) ) {
 			return( -EINTR );
 		}
-		spin_lock( &oob_priv->dp_port_lock );
-		spin_lock( &port_priv->dp_port_lock );
+		spin_lock_irqsave( &oob_priv->dp_port_lock, flags );
+		spin_lock_irqsave( &port_priv->dp_port_lock, flags );
 	}
 
-	/* command is 4 bytes: command, line, argument, pad */
 	data[0] = DIGI_CMD_SET_DTR_SIGNAL;
-	data[1] = port->number;
+	data[1] = port_priv->dp_port_num;
 	data[2] = (modem_signals&TIOCM_DTR) ?
 		DIGI_DTR_ACTIVE : DIGI_DTR_INACTIVE;
 	data[3] = 0;
 
 	data[4] = DIGI_CMD_SET_RTS_SIGNAL;
-	data[5] = port->number;
+	data[5] = port_priv->dp_port_num;
 	data[6] = (modem_signals&TIOCM_RTS) ?
 		DIGI_RTS_ACTIVE : DIGI_RTS_INACTIVE;
 	data[7] = 0;
@@ -413,18 +684,77 @@
 			ret );
 	}
 
-	spin_unlock( &port_priv->dp_port_lock );
-	spin_unlock( &oob_priv->dp_port_lock );
+	spin_unlock_irqrestore( &port_priv->dp_port_lock, flags );
+	spin_unlock_irqrestore( &oob_priv->dp_port_lock, flags );
 
 	return( ret );
 
 }
 
 
+/*
+*  Digi Transmit Idle
+*
+*  Digi transmit idle waits, up to timeout ticks, for the transmitter
+*  to go idle.  It returns 0 if successful or a negative error.
+*
+*  There are race conditions here if more than one process is calling
+*  digi_transmit_idle on the same port at the same time.  However, this
+*  is only called from close, and only one process can be in close on a
+*  port at a time, so its ok.
+*/
+
+static int digi_transmit_idle( struct usb_serial_port *port,
+	unsigned long timeout )
+{
+
+	int ret;
+	unsigned char buf[2];
+	digi_private_t *priv = (digi_private_t *)(port->private);
+	unsigned long flags = 0;
+
+
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
+	priv->dp_transmit_idle = 0;
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+
+	buf[0] = DIGI_CMD_TRANSMIT_IDLE;
+	buf[1] = 0;
+
+	if( (ret=digi_write_inb_command( port, buf, 2 )) != 0 )
+		return( ret );
+
+	timeout += jiffies;
+
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
+
+	while( jiffies < timeout && !priv->dp_transmit_idle ) {
+		cond_wait_interruptible_timeout_irqrestore(
+			&transmit_idle_wait, DIGI_RETRY_TIMEOUT,
+			&priv->dp_port_lock, flags );
+		if( signal_pending(current) ) {
+			return( -EINTR );
+		}
+		spin_lock_irqsave( &priv->dp_port_lock, flags );
+	}
+
+	priv->dp_transmit_idle = 0;
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+
+	return( 0 );
+
+}
+
+
 static void digi_rx_throttle( struct usb_serial_port *port )
 {
 
-dbg( "digi_rx_throttle: TOP: port=%d", port->number );
+#ifdef DEBUG
+	digi_private_t *priv = (digi_private_t *)(port->private);
+#endif
+
+
+dbg( "digi_rx_throttle: TOP: port=%d", priv->dp_port_num );
 
 	/* stop receiving characters. We just turn off the URB request, and
 	   let chars pile up in the device. If we're doing hardware
@@ -441,7 +771,12 @@
 static void digi_rx_unthrottle( struct usb_serial_port *port )
 {
 
-dbg( "digi_rx_unthrottle: TOP: port=%d", port->number );
+#ifdef DEBUG
+	digi_private_t *priv = (digi_private_t *)(port->private);
+#endif
+
+
+dbg( "digi_rx_unthrottle: TOP: port=%d", priv->dp_port_num );
 
 	/* just restart the receive interrupt URB */
 	//if (usb_submit_urb(port->interrupt_in_urb))
@@ -454,6 +789,7 @@
 	struct termios *old_termios )
 {
 
+	digi_private_t *priv = (digi_private_t *)(port->private);
 	unsigned int iflag = port->tty->termios->c_iflag;
 	unsigned int cflag = port->tty->termios->c_cflag;
 	unsigned int old_iflag = old_termios->c_iflag;
@@ -463,7 +799,7 @@
 	int i = 0;
 
 
-dbg( "digi_set_termios: TOP: port=%d, iflag=0x%x, old_iflag=0x%x, cflag=0x%x, old_cflag=0x%x", port->number, iflag, old_iflag, cflag, old_cflag );
+dbg( "digi_set_termios: TOP: port=%d, iflag=0x%x, old_iflag=0x%x, cflag=0x%x, old_cflag=0x%x", priv->dp_port_num, iflag, old_iflag, cflag, old_cflag );
 
 	/* set baud rate */
 	if( (cflag&CBAUD) != (old_cflag&CBAUD) ) {
@@ -506,7 +842,7 @@
 
 		if( arg != -1 ) {
 			buf[i++] = DIGI_CMD_SET_BAUD_RATE;
-			buf[i++] = port->number;
+			buf[i++] = priv->dp_port_num;
 			buf[i++] = arg;
 			buf[i++] = 0;
 		}
@@ -526,7 +862,7 @@
 		}
 
 		buf[i++] = DIGI_CMD_SET_PARITY;
-		buf[i++] = port->number;
+		buf[i++] = priv->dp_port_num;
 		buf[i++] = arg;
 		buf[i++] = 0;
 
@@ -550,7 +886,7 @@
 
 		if( arg != -1 ) {
 			buf[i++] = DIGI_CMD_SET_WORD_SIZE;
-			buf[i++] = port->number;
+			buf[i++] = priv->dp_port_num;
 			buf[i++] = arg;
 			buf[i++] = 0;
 		}
@@ -566,7 +902,7 @@
 			arg = DIGI_STOP_BITS_1;
 
 		buf[i++] = DIGI_CMD_SET_STOP_BITS;
-		buf[i++] = port->number;
+		buf[i++] = priv->dp_port_num;
 		buf[i++] = arg;
 		buf[i++] = 0;
 
@@ -589,7 +925,7 @@
 			arg &= ~DIGI_INPUT_FLOW_CONTROL_RTS;
 
 		buf[i++] = DIGI_CMD_SET_INPUT_FLOW_CONTROL;
-		buf[i++] = port->number;
+		buf[i++] = priv->dp_port_num;
 		buf[i++] = arg;
 		buf[i++] = 0;
 
@@ -612,7 +948,7 @@
 			arg &= ~DIGI_OUTPUT_FLOW_CONTROL_CTS;
 
 		buf[i++] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL;
-		buf[i++] = port->number;
+		buf[i++] = priv->dp_port_num;
 		buf[i++] = arg;
 		buf[i++] = 0;
 
@@ -627,13 +963,13 @@
 			arg = DIGI_DISABLE;
 
 		buf[i++] = DIGI_CMD_RECEIVE_ENABLE;
-		buf[i++] = port->number;
+		buf[i++] = priv->dp_port_num;
 		buf[i++] = arg;
 		buf[i++] = 0;
 
 	}
 
-	if( (ret=digi_write_oob( buf, i )) != 0 )
+	if( (ret=digi_write_oob_command( buf, i )) != 0 )
 		dbg( "digi_set_termios: write oob failed, ret=%d", ret );
 
 }
@@ -641,7 +977,14 @@
 
 static void digi_break_ctl( struct usb_serial_port *port, int break_state )
 {
-dbg( "digi_break_ctl: TOP: port=%d", port->number );
+
+#ifdef DEBUG
+	digi_private_t *priv = (digi_private_t *)(port->private);
+#endif
+
+
+dbg( "digi_break_ctl: TOP: port=%d", priv->dp_port_num );
+
 }
 
 
@@ -651,16 +994,17 @@
 
 	digi_private_t *priv = (digi_private_t *)(port->private);
 	unsigned int val;
+	unsigned long flags = 0;
 
 
-dbg( "digi_ioctl: TOP: port=%d, cmd=0x%x", port->number, cmd );
+dbg( "digi_ioctl: TOP: port=%d, cmd=0x%x", priv->dp_port_num, cmd );
 
 	switch (cmd) {
 
 	case TIOCMGET:
-		spin_lock( &priv->dp_port_lock );
+		spin_lock_irqsave( &priv->dp_port_lock, flags );
 		val = priv->dp_modem_signals;
-		spin_unlock( &priv->dp_port_lock );
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 		if( copy_to_user((unsigned int *)arg, &val, sizeof(int)) )
 			return( -EFAULT );
 		return( 0 );
@@ -670,12 +1014,12 @@
 	case TIOCMBIC:
 		if( copy_from_user(&val, (unsigned int *)arg, sizeof(int)) )
 			return( -EFAULT );
-		spin_lock( &priv->dp_port_lock );
+		spin_lock_irqsave( &priv->dp_port_lock, flags );
 		if( cmd == TIOCMBIS )
 			val = priv->dp_modem_signals | val;
 		else if( cmd == TIOCMBIC )
 			val = priv->dp_modem_signals & ~val;
-		spin_unlock( &priv->dp_port_lock );
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 		return( digi_set_modem_signals( port, val ) );
 
 	case TIOCMIWAIT:
@@ -701,15 +1045,17 @@
 
 	int ret,data_len,new_len;
 	digi_private_t *priv = (digi_private_t *)(port->private);
+	unsigned char *data = port->write_urb->transfer_buffer;
+	unsigned long flags = 0;
 
 
 dbg( "digi_write: TOP: port=%d, count=%d, from_user=%d, in_interrupt=%d",
-port->number, count, from_user, in_interrupt() );
+priv->dp_port_num, count, from_user, in_interrupt() );
 
 	/* be sure only one write proceeds at a time */
 	/* there are races on the port private buffer */
 	/* and races to check write_urb->status */
-	spin_lock( &priv->dp_port_lock );
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
 
 	/* wait for urb status clear to submit another urb */
 	if( port->write_urb->status == -EINPROGRESS ) {
@@ -724,7 +1070,7 @@
 			new_len = 0;
 		}
 
-		spin_unlock( &priv->dp_port_lock );
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 
 		return( new_len );
 
@@ -736,43 +1082,41 @@
 	data_len = new_len + priv->dp_buf_len;
 
 	if( data_len == 0 ) {
-		spin_unlock( &priv->dp_port_lock );
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 		return( 0 );
 	}
 
-	*((unsigned char *)(port->write_urb->transfer_buffer))
-		= (unsigned char)DIGI_CMD_SEND_DATA;
-	*((unsigned char *)(port->write_urb->transfer_buffer)+1)
-		= (unsigned char)data_len;
-
 	port->write_urb->transfer_buffer_length = data_len+2;
 
+	*data++ = DIGI_CMD_SEND_DATA;
+	*data++ = data_len;
+
 	/* copy in buffered data first */
-	memcpy( port->write_urb->transfer_buffer+2, priv->dp_buf,
-		priv->dp_buf_len );
+	memcpy( data, priv->dp_buf, priv->dp_buf_len );
+	data += priv->dp_buf_len;
 
 	/* copy in new data */
 	if( from_user ) {
-		copy_from_user(
-			port->write_urb->transfer_buffer+2+priv->dp_buf_len,
-			buf, new_len );
+		if( copy_from_user( data, buf, new_len ) ) {
+			spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+			return( -EFAULT );
+		}
 	} else {
-		memcpy( port->write_urb->transfer_buffer+2+priv->dp_buf_len,
-			buf, new_len );
+		memcpy( data, buf, new_len );
 	}  
 
 #ifdef DEBUG_DATA
-{
+ {
 	int i;
 
 	printk( KERN_DEBUG __FILE__ ": digi_write: port=%d, length=%d, data=",
-		port->number, port->write_urb->transfer_buffer_length );
+		priv->dp_port_num, port->write_urb->transfer_buffer_length );
 	for( i=0; i<port->write_urb->transfer_buffer_length; ++i ) {
 		printk( "%.2x ",
 		((unsigned char *)port->write_urb->transfer_buffer)[i] );
 	}
 	printk( "\n" );
-}
+ }
 #endif
 
 	if( (ret=usb_submit_urb(port->write_urb)) == 0 ) {
@@ -781,13 +1125,11 @@
 	} else {
 		dbg( "digi_write: usb_submit_urb failed, ret=%d",
 			ret );
-		/* no bytes written - should we return the error code or 0? */
-		ret = 0;
 	}
 
 	/* return length of new data written, or error */
 dbg( "digi_write: returning %d", ret );
-	spin_unlock( &priv->dp_port_lock );
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 	return( ret );
 
 } 
@@ -798,17 +1140,18 @@
 
 	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	struct usb_serial *serial = port->serial;
-	struct tty_struct *tty = port->tty;
 	digi_private_t *priv = (digi_private_t *)(port->private);
 	int ret;
 
 
-dbg( "digi_write_bulk_callback: TOP: port=%d", port->number );
+dbg( "digi_write_bulk_callback: TOP: port=%d", priv->dp_port_num );
 
 	/* handle callback on out-of-band port */
-	if( port->number == oob_port_num ) {
+	if( priv->dp_port_num == oob_port_num ) {
 		dbg( "digi_write_bulk_callback: oob callback" );
+		spin_lock( &priv->dp_port_lock );
 		wake_up_interruptible( &port->write_wait );
+		spin_unlock( &priv->dp_port_lock );
 		return;
 	}
 
@@ -833,17 +1176,17 @@
 			priv->dp_buf_len );
 
 #ifdef DEBUG_DATA
-{
+ {
 	int i;
 
 	printk( KERN_DEBUG __FILE__ ": digi_write_bulk_callback: port=%d, length=%d, data=",
-		port->number, port->write_urb->transfer_buffer_length );
+		priv->dp_port_num, port->write_urb->transfer_buffer_length );
 	for( i=0; i<port->write_urb->transfer_buffer_length; ++i ) {
 		printk( "%.2x ",
 		((unsigned char *)port->write_urb->transfer_buffer)[i] );
 	}
 	printk( "\n" );
-}
+ }
 #endif
 
 		if( (ret=usb_submit_urb(port->write_urb)) == 0 ) {
@@ -853,19 +1196,17 @@
 		}
 
 	}
-	spin_unlock( &priv->dp_port_lock );
 
-	/* wake up port processes */
-	wake_up_interruptible( &port->write_wait );
+	/* wake up processes sleeping on writes immediately */
+	digi_wakeup_write( port );
 
-	/* wake up line discipline */
-	tty = port->tty;
-	if( (tty->flags & (1 << TTY_DO_WRITE_WAKEUP))
-	&& tty->ldisc.write_wakeup )
-		(tty->ldisc.write_wakeup)(tty);
+	spin_unlock( &priv->dp_port_lock );
 
-	/* wake up other tty processes */
-	wake_up_interruptible( &tty->write_wait );
+	/* also queue up a wakeup at scheduler time, in case we */
+	/* lost the race in write_chan(). */
+	priv->dp_tasks.routine = (void *)digi_wakeup_write_lock;
+	priv->dp_tasks.data = (void *)port;
+	queue_task( &(priv->dp_tasks), &tq_scheduler );
 
 }
 
@@ -875,20 +1216,21 @@
 
 	int room;
 	digi_private_t *priv = (digi_private_t *)(port->private);
+	unsigned long flags = 0;
 
 
-dbg( "digi_write_room: TOP: port=%d", port->number );
+dbg( "digi_write_room: TOP: port=%d", priv->dp_port_num );
 
-	spin_lock( &priv->dp_port_lock );
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
 
 	if( port->write_urb->status == -EINPROGRESS )
 		room = 0;
 	else
 		room = port->bulk_out_size - 2 - priv->dp_buf_len;
 
-	spin_unlock( &priv->dp_port_lock );
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 
-dbg( "digi_write_room: port=%d, room=%d", port->number, room );
+dbg( "digi_write_room: port=%d, room=%d", priv->dp_port_num, room );
 	return( room );
 
 }
@@ -900,13 +1242,14 @@
 	digi_private_t *priv = (digi_private_t *)(port->private);
 
 
-dbg( "digi_chars_in_buffer: TOP: port=%d", port->number );
+dbg( "digi_chars_in_buffer: TOP: port=%d", priv->dp_port_num );
 
 	if( port->write_urb->status == -EINPROGRESS ) {
-dbg( "digi_chars_in_buffer: port=%d, chars=%d", port->number, port->bulk_out_size - 2 );
-		return( port->bulk_out_size - 2 );
+dbg( "digi_chars_in_buffer: port=%d, chars=%d", priv->dp_port_num, port->bulk_out_size - 2 );
+		/* return( port->bulk_out_size - 2 ); */
+		return( 256 );
 	} else {
-dbg( "digi_chars_in_buffer: port=%d, chars=%d", port->number, priv->dp_buf_len );
+dbg( "digi_chars_in_buffer: port=%d, chars=%d", priv->dp_port_num, priv->dp_buf_len );
 		return( priv->dp_buf_len );
 	}
 
@@ -916,43 +1259,66 @@
 static int digi_open( struct usb_serial_port *port, struct file *filp )
 {
 
-	int i = 0;
 	int ret;
 	unsigned char buf[32];
 	digi_private_t *priv = (digi_private_t *)(port->private);
 	struct termios not_termios;
+	unsigned long flags = 0;
 
 
-dbg( "digi_open: TOP: port %d, active:%d", port->number, port->active );
+dbg( "digi_open: TOP: port %d, active:%d", priv->dp_port_num, port->active );
 
 	/* be sure the device is started up */
 	if( digi_startup_device( port->serial ) != 0 )
 		return( -ENXIO );
 
-	MOD_INC_USE_COUNT;
-
 	/* if port is already open, just return */
 	/* be sure exactly one open proceeds */
-	spin_lock( &priv->dp_port_lock );
-	if( port->active++ ) {
-		spin_unlock( &priv->dp_port_lock );
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
+	if( port->active >= 1 && !priv->dp_in_close ) {
+		++port->active;
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+		MOD_INC_USE_COUNT;
 		return( 0 );
 	}
-	spin_unlock( &priv->dp_port_lock );
+
+	/* don't wait on a close in progress for non-blocking opens */
+	if( priv->dp_in_close && (filp->f_flags&(O_NDELAY|O_NONBLOCK)) == 0 ) {
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+		return( -EAGAIN );
+	}
+
+	/* prevent other opens from proceeding, before giving up lock */
+	++port->active;
+
+	/* wait for close to finish */
+	while( priv->dp_in_close ) {
+		cond_wait_interruptible_timeout_irqrestore(
+			&priv->dp_close_wait, DIGI_RETRY_TIMEOUT,
+			&priv->dp_port_lock, flags );
+		if( signal_pending(current) ) {
+			--port->active;
+			return( -EINTR );
+		}
+		spin_lock_irqsave( &priv->dp_port_lock, flags );
+	}
+
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+	MOD_INC_USE_COUNT;
  
 	/* read modem signals automatically whenever they change */
-	buf[i++] = DIGI_CMD_READ_INPUT_SIGNALS;
-	buf[i++] = port->number;
-	buf[i++] = DIGI_ENABLE;
-	buf[i++] = 0;
+	buf[0] = DIGI_CMD_READ_INPUT_SIGNALS;
+	buf[1] = priv->dp_port_num;
+	buf[2] = DIGI_ENABLE;
+	buf[3] = 0;
 
 	/* flush fifos */
-	buf[i++] = DIGI_CMD_IFLUSH_FIFO;
-	buf[i++] = port->number;
-	buf[i++] = DIGI_FLUSH_TX | DIGI_FLUSH_RX;
-	buf[i++] = 0;
+	buf[4] = DIGI_CMD_IFLUSH_FIFO;
+	buf[5] = priv->dp_port_num;
+	buf[6] = DIGI_FLUSH_TX | DIGI_FLUSH_RX;
+	buf[7] = 0;
 
-	if( (ret=digi_write_oob( buf, i )) != 0 )
+	if( (ret=digi_write_oob_command( buf, 8 )) != 0 )
 		dbg( "digi_open: write oob failed, ret=%d", ret );
 
 	/* set termios settings */
@@ -971,58 +1337,83 @@
 static void digi_close( struct usb_serial_port *port, struct file *filp )
 {
 
-	int i = 0;
 	int ret;
 	unsigned char buf[32];
+	struct tty_struct *tty = port->tty;
 	digi_private_t *priv = (digi_private_t *)(port->private);
+	unsigned long flags = 0;
 
 
-dbg( "digi_close: TOP: port %d, active:%d", port->number, port->active );
+dbg( "digi_close: TOP: port %d, active:%d", priv->dp_port_num, port->active );
 
 
 	/* do cleanup only after final close on this port */
-	spin_lock( &priv->dp_port_lock );
-	if( --port->active ) {
-		spin_unlock( &priv->dp_port_lock );
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
+	if( port->active > 1 ) {
+		--port->active;
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
 		MOD_DEC_USE_COUNT;
 		return;
+	} else if( port->active <= 0 ) {
+		spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+		return;
 	}
-	spin_unlock( &priv->dp_port_lock );
-	
+	priv->dp_in_close = 1;
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+
+	/* tell line discipline to process only XON/XOFF */
+        tty->closing = 1;
+
+	/* wait for output to drain */
+	if( (filp->f_flags&(O_NDELAY|O_NONBLOCK)) == 0 ) {
+		tty_wait_until_sent( tty, DIGI_CLOSE_TIMEOUT );
+	}
+
+	/* flush driver and line discipline buffers */
+	if( tty->driver.flush_buffer )
+		tty->driver.flush_buffer( tty );
+	if( tty->ldisc.flush_buffer )
+		tty->ldisc.flush_buffer( tty );
+
+	/* wait for transmit idle */
+	if( (filp->f_flags&(O_NDELAY|O_NONBLOCK)) == 0 ) {
+		digi_transmit_idle( port, DIGI_CLOSE_TIMEOUT );
+	}
+
 	/* drop DTR and RTS */
 	digi_set_modem_signals( port, 0 );
 
 	/* disable input flow control */
-	buf[i++] = DIGI_CMD_SET_INPUT_FLOW_CONTROL;
-	buf[i++] = port->number;
-	buf[i++] = DIGI_DISABLE;
-	buf[i++] = 0;
+	buf[0] = DIGI_CMD_SET_INPUT_FLOW_CONTROL;
+	buf[1] = priv->dp_port_num;
+	buf[2] = DIGI_DISABLE;
+	buf[3] = 0;
 
 	/* disable output flow control */
-	buf[i++] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL;
-	buf[i++] = port->number;
-	buf[i++] = DIGI_DISABLE;
-	buf[i++] = 0;
+	buf[4] = DIGI_CMD_SET_OUTPUT_FLOW_CONTROL;
+	buf[5] = priv->dp_port_num;
+	buf[6] = DIGI_DISABLE;
+	buf[7] = 0;
 
 	/* disable reading modem signals automatically */
-	buf[i++] = DIGI_CMD_READ_INPUT_SIGNALS;
-	buf[i++] = port->number;
-	buf[i++] = DIGI_DISABLE;
-	buf[i++] = 0;
+	buf[8] = DIGI_CMD_READ_INPUT_SIGNALS;
+	buf[9] = priv->dp_port_num;
+	buf[10] = DIGI_DISABLE;
+	buf[11] = 0;
 
 	/* flush fifos */
-	buf[i++] = DIGI_CMD_IFLUSH_FIFO;
-	buf[i++] = port->number;
-	buf[i++] = DIGI_FLUSH_TX | DIGI_FLUSH_RX;
-	buf[i++] = 0;
+	buf[12] = DIGI_CMD_IFLUSH_FIFO;
+	buf[13] = priv->dp_port_num;
+	buf[14] = DIGI_FLUSH_TX | DIGI_FLUSH_RX;
+	buf[15] = 0;
 
 	/* disable receive */
-	buf[i++] = DIGI_CMD_RECEIVE_ENABLE;
-	buf[i++] = port->number;
-	buf[i++] = DIGI_DISABLE;
-	buf[i++] = 0;
+	buf[16] = DIGI_CMD_RECEIVE_ENABLE;
+	buf[17] = priv->dp_port_num;
+	buf[18] = DIGI_DISABLE;
+	buf[19] = 0;
 
-	if( (ret=digi_write_oob( buf, i )) != 0 )
+	if( (ret=digi_write_oob_command( buf, 20 )) != 0 )
 		dbg( "digi_close: write oob failed, ret=%d", ret );
 
 	/* wait for final commands on oob port to complete */
@@ -1033,12 +1424,21 @@
 			break;
 		}
 	}
-	
+
 	/* shutdown any outstanding bulk writes */
 	usb_unlink_urb (port->write_urb);
 
+	tty->closing = 0;
+
+	spin_lock_irqsave( &priv->dp_port_lock, flags );
+	--port->active;
+	priv->dp_in_close = 0;
+	wake_up_interruptible( &priv->dp_close_wait );
+	spin_unlock_irqrestore( &priv->dp_port_lock, flags );
+
 	MOD_DEC_USE_COUNT;
 
+dbg( "digi_close: done" );
 }
 
 
@@ -1093,6 +1493,8 @@
 dbg( "digi_startup: TOP" );
 
 	spin_lock_init( &startup_lock );
+	init_waitqueue_head( &modem_change_wait );
+	init_waitqueue_head( &transmit_idle_wait );
 
 	/* allocate the private data structures for all ports */
 	/* number of regular ports + 1 for the out-of-band port */
@@ -1108,8 +1510,14 @@
 			return( 1 );			/* error */
 
 		/* initialize private structure */
+		priv->dp_port_num = i;
 		priv->dp_buf_len = 0;
 		priv->dp_modem_signals = 0;
+		priv->dp_transmit_idle = 0;
+		priv->dp_in_close = 0;
+		init_waitqueue_head( &priv->dp_close_wait );
+		priv->dp_tasks.next = NULL;
+		priv->dp_tasks.data = NULL;
 		spin_lock_init( &priv->dp_port_lock );
 
 		/* initialize write wait queue for this port */
@@ -1157,6 +1565,7 @@
 	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	struct usb_serial *serial = port->serial;
 	struct tty_struct *tty = port->tty;
+	digi_private_t *priv = (digi_private_t *)(port->private);
 	int opcode = ((unsigned char *)urb->transfer_buffer)[0];
 	int len = ((unsigned char *)urb->transfer_buffer)[1];
 	int status = ((unsigned char *)urb->transfer_buffer)[2];
@@ -1164,11 +1573,11 @@
 	int ret,i;
 
 
-dbg( "digi_read_bulk_callback: TOP: port=%d", port->number );
+dbg( "digi_read_bulk_callback: TOP: port=%d", priv->dp_port_num );
 
 	/* handle oob callback */
-	if( port->number == oob_port_num ) {
-		digi_read_oob( urb );
+	if( priv->dp_port_num == oob_port_num ) {
+		digi_read_oob_callback( urb );
 		return;
 	}
 
@@ -1181,13 +1590,15 @@
 	if( urb->status ) {
 		dbg( "digi_read_bulk_callback: nonzero read bulk status: %d",
 			urb->status );
+		if( urb->status == -ENOENT )
+			return;
 		goto resubmit;
 	}
 
 #ifdef DEBUG_DATA
 if( urb->actual_length ) {
 	printk( KERN_DEBUG __FILE__ ": digi_read_bulk_callback: port=%d, length=%d, data=",
-		port->number, urb->actual_length );
+		priv->dp_port_num, urb->actual_length );
 	for( i=0; i<urb->actual_length; ++i ) {
 		printk( "%.2x ", ((unsigned char *)urb->transfer_buffer)[i] );
 	}
@@ -1196,7 +1607,7 @@
 #endif
 
 	if( urb->actual_length != len + 2 )
-     		err( KERN_INFO "digi_read_bulk_callback: INCOMPLETE PACKET, port=%d, opcode=%d, len=%d, actual_length=%d, status=%d", port->number, opcode, len, urb->actual_length, status );
+     		err( KERN_INFO "digi_read_bulk_callback: INCOMPLETE PACKET, port=%d, opcode=%d, len=%d, actual_length=%d, status=%d", priv->dp_port_num, opcode, len, urb->actual_length, status );
 
 	/* receive data */
 	if( opcode == DIGI_CMD_RECEIVE_DATA && urb->actual_length > 3 ) {
@@ -1209,67 +1620,89 @@
 
 	/* continue read */
 resubmit:
-	if( (ret=usb_submit_urb(urb)) != 0 )
+	if( (ret=usb_submit_urb(urb)) != 0 ) {
 		dbg( "digi_read_bulk_callback: failed resubmitting urb, ret=%d",
 			ret );
+	}
 
 }
 
 
-static void digi_read_oob( struct urb *urb )
+static void digi_read_oob_callback( struct urb *urb )
 {
 
 	struct usb_serial_port *port = (struct usb_serial_port *)urb->context;
 	struct usb_serial *serial = port->serial;
 	digi_private_t *priv;
-	int oob_opcode = ((unsigned char *)urb->transfer_buffer)[0];
-	int oob_line = ((unsigned char *)urb->transfer_buffer)[1];
-	int oob_status = ((unsigned char *)urb->transfer_buffer)[2];
-	int oob_ret = ((unsigned char *)urb->transfer_buffer)[3];
-	int ret;
+	int opcode, line, status, val;
+	int i,ret;
 
 
-dbg( "digi_read_oob: opcode=%d, line=%d, status=%d, ret=%d", oob_opcode, oob_line, oob_status, oob_ret );
+dbg( "digi_read_oob_callback: len=%d", urb->actual_length );
 
 	if( urb->status ) {
-		dbg( "digi_read_oob: nonzero read bulk status on oob: %d",
+		dbg( "digi_read_oob_callback: nonzero read bulk status on oob: %d",
 			urb->status );
+		if( urb->status == -ENOENT )
+			return;
 		goto resubmit;
 	}
 
-	if( oob_opcode == DIGI_CMD_READ_INPUT_SIGNALS && oob_status == 0 ) {
+	for( i=0; i<urb->actual_length-3; ) {
 
-		priv = serial->port[oob_line].private;
+		opcode = ((unsigned char *)urb->transfer_buffer)[i++];
+		line = ((unsigned char *)urb->transfer_buffer)[i++];
+		status = ((unsigned char *)urb->transfer_buffer)[i++];
+		val = ((unsigned char *)urb->transfer_buffer)[i++];
 
-		spin_lock( &priv->dp_port_lock );
+dbg( "digi_read_oob_callback: opcode=%d, line=%d, status=%d, val=%d", opcode, line, status, val );
 
-		/* convert from digi flags to termiox flags */
-		if( oob_ret & DIGI_READ_INPUT_SIGNALS_CTS )
-			priv->dp_modem_signals |= TIOCM_CTS;
-		else
-			priv->dp_modem_signals &= ~TIOCM_CTS;
-		if( oob_ret & DIGI_READ_INPUT_SIGNALS_DSR )
-			priv->dp_modem_signals |= TIOCM_DSR;
-		else
-			priv->dp_modem_signals &= ~TIOCM_DSR;
-		if( oob_ret & DIGI_READ_INPUT_SIGNALS_RI )
-			priv->dp_modem_signals |= TIOCM_RI;
-		else
-			priv->dp_modem_signals &= ~TIOCM_RI;
-		if( oob_ret & DIGI_READ_INPUT_SIGNALS_DCD )
-			priv->dp_modem_signals |= TIOCM_CD;
-		else
-			priv->dp_modem_signals &= ~TIOCM_CD;
+		if( status != 0 )
+			continue;
 
-		spin_unlock( &priv->dp_port_lock );
+		priv = serial->port[line].private;
+
+		if( opcode == DIGI_CMD_READ_INPUT_SIGNALS ) {
+
+			spin_lock( &priv->dp_port_lock );
+
+			/* convert from digi flags to termiox flags */
+			if( val & DIGI_READ_INPUT_SIGNALS_CTS )
+				priv->dp_modem_signals |= TIOCM_CTS;
+			else
+				priv->dp_modem_signals &= ~TIOCM_CTS;
+			if( val & DIGI_READ_INPUT_SIGNALS_DSR )
+				priv->dp_modem_signals |= TIOCM_DSR;
+			else
+				priv->dp_modem_signals &= ~TIOCM_DSR;
+			if( val & DIGI_READ_INPUT_SIGNALS_RI )
+				priv->dp_modem_signals |= TIOCM_RI;
+			else
+				priv->dp_modem_signals &= ~TIOCM_RI;
+			if( val & DIGI_READ_INPUT_SIGNALS_DCD )
+				priv->dp_modem_signals |= TIOCM_CD;
+			else
+				priv->dp_modem_signals &= ~TIOCM_CD;
+
+			wake_up_interruptible( &modem_change_wait );
+			spin_unlock( &priv->dp_port_lock );
+
+		} else if( opcode == DIGI_CMD_TRANSMIT_IDLE ) {
+
+			spin_lock( &priv->dp_port_lock );
+			priv->dp_transmit_idle = 1;
+			wake_up_interruptible( &transmit_idle_wait );
+			spin_unlock( &priv->dp_port_lock );
+
+		}
 
 	}
 
+
 resubmit:
 	if( (ret=usb_submit_urb(urb)) != 0 ) {
-		dbg( "digi_read_oob: failed resubmitting oob urb, ret=%d",
+		dbg( "digi_read_oob_callback: failed resubmitting oob urb, ret=%d",
 		ret );
 	}
 
 }
-

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)