diff options
Diffstat (limited to 'drivers/usb/serial/keyspan_pda.c')
-rw-r--r-- | drivers/usb/serial/keyspan_pda.c | 188 |
1 files changed, 107 insertions, 81 deletions
diff --git a/drivers/usb/serial/keyspan_pda.c b/drivers/usb/serial/keyspan_pda.c index 675764ad4..e118e29b8 100644 --- a/drivers/usb/serial/keyspan_pda.c +++ b/drivers/usb/serial/keyspan_pda.c @@ -1,8 +1,9 @@ /* * USB Keyspan PDA Converter driver * - * Copyright (C) 1999, 2000 - * Greg Kroah-Hartman (greg@kroah.com) + * Copyright (c) 1999, 2000 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (c) 1999, 2000 Brian Warner <warner@lothar.com> + * Copyright (c) 2000 Al Borchers <borchers@steinerpoint.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -11,6 +12,24 @@ * * See Documentation/usb/usb-serial.txt for more information on using this driver * + * (07/20/2000) borchers + * - keyspan_pda_write no longer sleeps if it is called on interrupt time; + * PPP and the line discipline with stty echo on can call write on + * interrupt time and this would cause an oops if write slept + * - if keyspan_pda_write is in an interrupt, it will not call + * usb_control_msg (which sleeps) to query the room in the device + * buffer, it simply uses the current room value it has + * - if the urb is busy or if it is throttled keyspan_pda_write just + * returns 0, rather than sleeping to wait for this to change; the + * write_chan code in n_tty.c will sleep if needed before calling + * keyspan_pda_write again + * - if the device needs to be unthrottled, write now queues up the + * call to usb_control_msg (which sleeps) to unthrottle the device + * - the wakeups from keyspan_pda_write_bulk_callback are queued rather + * than done directly from the callback to avoid the race in write_chan + * - keyspan_pda_chars_in_buffer also indicates its buffer is full if the + * urb status is -EINPROGRESS, meaning it cannot write at the moment + * * (07/19/2000) gkh * Added module_init and module_exit functions to handle the fact that this * driver is a loadable module now. @@ -35,6 +54,7 @@ #include <linux/tty.h> #include <linux/module.h> #include <linux/spinlock.h> +#include <linux/tqueue.h> #ifdef CONFIG_USB_SERIAL_DEBUG #define DEBUG @@ -56,6 +76,8 @@ struct ezusb_hex_record { struct keyspan_pda_private { int tx_room; int tx_throttled; + struct tq_struct wakeup_task; + struct tq_struct unthrottle_task; }; #define KEYSPAN_VENDOR_ID 0x06cd @@ -69,6 +91,45 @@ static __u16 keyspan_pda_product_id = KEYSPAN_PDA_ID; +static void keyspan_pda_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 ); + /* For 2.2.16 backport -- wake_up_interruptible( &tty->poll_wait ); */ + +} + +static void keyspan_pda_request_unthrottle( struct usb_serial *serial ) +{ + + dbg(" request_unthrottle"); + /* ask the device to tell us when the tx buffer becomes + sufficiently empty */ + usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + 7, /* request_unthrottle */ + USB_TYPE_VENDOR | USB_RECIP_INTERFACE + | USB_DIR_OUT, + 16, /* value: threshold */ + 0, /* index */ + NULL, + 0, + 2*HZ); + +} + + static void keyspan_pda_rx_interrupt (struct urb *urb) { struct usb_serial_port *port = (struct usb_serial_port *)urb->context; @@ -113,8 +174,8 @@ static void keyspan_pda_rx_interrupt (struct urb *urb) case 2: /* tx unthrottle interrupt */ tty = serial->port[0].tty; priv->tx_throttled = 0; - wake_up(&port->write_wait); /* wake up writer */ - wake_up(&tty->write_wait); /* them too */ + /* queue up a wakeup at scheduler time */ + queue_task( &priv->wakeup_task, &tq_scheduler ); break; default: break; @@ -355,7 +416,6 @@ static int keyspan_pda_write(struct usb_serial_port *port, int from_user, int request_unthrottle = 0; int rc = 0; struct keyspan_pda_private *priv; - DECLARE_WAITQUEUE(wait, current); priv = (struct keyspan_pda_private *)(port->private); /* guess how much room is left in the device's ring buffer, and if we @@ -376,53 +436,22 @@ static int keyspan_pda_write(struct usb_serial_port *port, int from_user, the TX urb is in-flight (wait until it completes) the device is full (wait until it says there is room) */ - while (port->write_urb->status == -EINPROGRESS) { - if (0 /* file->f_flags & O_NONBLOCK */) { - rc = -EAGAIN; - goto err; - } - interruptible_sleep_on(&port->write_wait); - if (signal_pending(current)) { - rc = -ERESTARTSYS; - goto err; - } + if (port->write_urb->status == -EINPROGRESS || priv->tx_throttled ) { + return( 0 ); } - /* at this point the URB is in our control, nobody else can submit it + /* At this point the URB is in our control, nobody else can submit it again (the only sudden transition was the one from EINPROGRESS to - finished) */ - - /* the next potential block is that our TX process might be throttled. - The transition from throttled->not happens because of an Rx - interrupt, and the wake_up occurs during the same interrupt, so we - have to be careful to avoid a race that would cause us to sleep - forever. */ - - add_wait_queue(&port->write_wait, &wait); - set_current_state(TASK_INTERRUPTIBLE); - while (priv->tx_throttled) { - /* device can't accomodate any more characters. Sleep until it - can. Woken up by an Rx interrupt message, which clears - tx_throttled first. */ - dbg(" tx_throttled, going to sleep"); - if (signal_pending(current)) { - current->state = TASK_RUNNING; - remove_wait_queue(&port->write_wait, &wait); - dbg(" woke up because of signal"); - rc = -ERESTARTSYS; - goto err; - } - schedule(); - dbg(" woke up"); - } - remove_wait_queue(&port->write_wait, &wait); - set_current_state(TASK_RUNNING); + finished). Also, the tx process is not throttled. So we are + ready to write. */ count = (count > port->bulk_out_size) ? port->bulk_out_size : count; - if (count > priv->tx_room) { + + /* Check if we might overrun the Tx buffer. If so, ask the + device how much room it really has. This is done only on + scheduler time, since usb_control_msg() sleeps. */ + if (count > priv->tx_room && !in_interrupt()) { unsigned char room; - /* Looks like we might overrun the Tx buffer. Ask the device - how much room it really has */ rc = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0), 6, /* write_room */ @@ -443,19 +472,20 @@ static int keyspan_pda_write(struct usb_serial_port *port, int from_user, } dbg(" roomquery says %d", room); priv->tx_room = room; - if (count > priv->tx_room) { - /* we're about to completely fill the Tx buffer, so - we'll be throttled afterwards. */ - count = priv->tx_room; - request_unthrottle = 1; - } } - priv->tx_room -= count; + if (count > priv->tx_room) { + /* we're about to completely fill the Tx buffer, so + we'll be throttled afterwards. */ + count = priv->tx_room; + request_unthrottle = 1; + } if (count) { /* now transfer data */ if (from_user) { - copy_from_user(port->write_urb->transfer_buffer, buf, count); + if( copy_from_user(port->write_urb->transfer_buffer, + buf, count) ) + return( -EFAULT ); } else { memcpy (port->write_urb->transfer_buffer, buf, count); @@ -463,6 +493,8 @@ static int keyspan_pda_write(struct usb_serial_port *port, int from_user, /* send the data out the bulk port */ port->write_urb->transfer_buffer_length = count; + priv->tx_room -= count; + if (usb_submit_urb(port->write_urb)) dbg(" usb_submit_urb(write bulk) failed"); } @@ -473,25 +505,11 @@ static int keyspan_pda_write(struct usb_serial_port *port, int from_user, } if (request_unthrottle) { - dbg(" request_unthrottle"); - /* ask the device to tell us when the tx buffer becomes - sufficiently empty */ priv->tx_throttled = 1; /* block writers */ - rc = usb_control_msg(serial->dev, - usb_sndctrlpipe(serial->dev, 0), - 7, /* request_unthrottle */ - USB_TYPE_VENDOR | USB_RECIP_INTERFACE - | USB_DIR_OUT, - 16, /* value: threshold */ - 0, /* index */ - NULL, - 0, - 2*HZ); + queue_task( &priv->unthrottle_task, &tq_scheduler ); } return (count); - err: - return (rc); } @@ -499,7 +517,9 @@ static void keyspan_pda_write_bulk_callback (struct urb *urb) { struct usb_serial_port *port = (struct usb_serial_port *)urb->context; struct usb_serial *serial; - struct tty_struct *tty; + struct keyspan_pda_private *priv; + + priv = (struct keyspan_pda_private *)(port->private); if (port_paranoia_check (port, "keyspan_pda_rx_interrupt")) { return; @@ -510,14 +530,9 @@ static void keyspan_pda_write_bulk_callback (struct urb *urb) return; } - wake_up_interruptible(&port->write_wait); + /* queue up a wakeup at scheduler time */ + queue_task( &priv->wakeup_task, &tq_scheduler ); - tty = port->tty; - if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && - tty->ldisc.write_wakeup) - (tty->ldisc.write_wakeup)(tty); - - wake_up_interruptible(&tty->write_wait); } @@ -541,7 +556,7 @@ static int keyspan_pda_chars_in_buffer (struct usb_serial_port *port) /* when throttled, return at least WAKEUP_CHARS to tell select() (via n_tty.c:normal_poll() ) that we're not writeable. */ - if (priv->tx_throttled) + if( port->write_urb->status == -EINPROGRESS || priv->tx_throttled ) return 256; return 0; } @@ -644,14 +659,25 @@ static int keyspan_pda_fake_startup (struct usb_serial *serial) static int keyspan_pda_startup (struct usb_serial *serial) { + + struct keyspan_pda_private *priv; + /* allocate the private data structures for all ports. Well, for all one ports. */ - serial->port[0].private = kmalloc(sizeof(struct keyspan_pda_private), - GFP_KERNEL); - if (!serial->port[0].private) + priv = serial->port[0].private + = kmalloc(sizeof(struct keyspan_pda_private), GFP_KERNEL); + if (!priv) return (1); /* error */ init_waitqueue_head(&serial->port[0].write_wait); + priv->wakeup_task.next = NULL; + priv->wakeup_task.sync = 0; + priv->wakeup_task.routine = (void *)keyspan_pda_wakeup_write; + priv->wakeup_task.data = (void *)(&serial->port[0]); + priv->unthrottle_task.next = NULL; + priv->unthrottle_task.sync = 0; + priv->unthrottle_task.routine = (void *)keyspan_pda_request_unthrottle; + priv->unthrottle_task.data = (void *)(serial); return (0); } |