USB: make sure usb serial drivers don't flush to logically disconnected devices
[safe/jmp/linux-2.6] / drivers / usb / serial / option.c
index 89f067d..5e8bf1b 100644 (file)
@@ -38,6 +38,7 @@
 #include <linux/tty.h>
 #include <linux/tty_flip.h>
 #include <linux/module.h>
+#include <linux/bitops.h>
 #include <linux/usb.h>
 #include <linux/usb/serial.h>
 
@@ -107,18 +108,19 @@ static int  option_send_setup(struct usb_serial_port *port);
 #define HUAWEI_VENDOR_ID                       0x12D1
 #define HUAWEI_PRODUCT_E600                    0x1001
 #define HUAWEI_PRODUCT_E220                    0x1003
+#define HUAWEI_PRODUCT_E220BIS                 0x1004
 
 #define NOVATELWIRELESS_VENDOR_ID              0x1410
+#define DELL_VENDOR_ID                         0x413C
 
 #define ANYDATA_VENDOR_ID                      0x16d5
-#define ANYDATA_PRODUCT_ID                     0x6501
+#define ANYDATA_PRODUCT_ADU_E100A              0x6501
+#define ANYDATA_PRODUCT_ADU_500A               0x6502
 
 #define BANDRICH_VENDOR_ID                     0x1A8D
 #define BANDRICH_PRODUCT_C100_1                        0x1002
 #define BANDRICH_PRODUCT_C100_2                        0x1003
 
-#define DELL_VENDOR_ID                         0x413C
-
 static struct usb_device_id option_ids[] = {
        { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) },
        { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) },
@@ -156,7 +158,8 @@ static struct usb_device_id option_ids[] = {
        { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_KOI_MODEM) },
        { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_ETNA_KOI_NETWORK) },
        { USB_DEVICE(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600) },
-       { USB_DEVICE(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E220) },
+       { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E220, 0xff, 0xff, 0xff) },
+       { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E220BIS, 0xff, 0xff, 0xff) },
        { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1100) }, /* Novatel Merlin XS620/S640 */
        { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1110) }, /* Novatel Merlin S620 */
        { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x1120) }, /* Novatel Merlin EX720 */
@@ -169,10 +172,20 @@ static struct usb_device_id option_ids[] = {
        { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x2110) }, /* Novatel Merlin ES620 / Merlin ES720 / Ovation U720 */
        { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x2130) }, /* Novatel Merlin ES620 SM Bus */
        { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x2410) }, /* Novatel EU740 */
-       { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ID) },
+       { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x4100) }, /* Novatel U727 */
+       { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, 0x4400) }, /* Novatel MC950 */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8114) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite EV620 CDMA/EV-DO */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8115) }, /* Dell Wireless 5500 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8116) }, /* Dell Wireless 5505 Mobile Broadband HSDPA Mini-Card == Novatel Expedite EU740 HSDPA/3G */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8117) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO ExpressCard == Novatel Merlin XV620 CDMA/EV-DO */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8118) }, /* Dell Wireless 5510 Mobile Broadband HSDPA ExpressCard == Novatel Merlin XU870 HSDPA/3G */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8128) }, /* Dell Wireless 5700 Mobile Broadband CDMA/EVDO Mini-Card == Novatel Expedite E720 CDMA/EV-DO */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8136) }, /* Dell Wireless HSDPA 5520 == Novatel Expedite EU860D */
+       { USB_DEVICE(DELL_VENDOR_ID, 0x8137) }, /* Dell Wireless HSDPA 5520 */
+       { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_E100A) },
+       { USB_DEVICE(ANYDATA_VENDOR_ID, ANYDATA_PRODUCT_ADU_500A) },
        { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_1) },
        { USB_DEVICE(BANDRICH_VENDOR_ID, BANDRICH_PRODUCT_C100_2) },
-       { USB_DEVICE(DELL_VENDOR_ID, 0x8118) },         /* Dell Wireless 5510 Mobile Broadband HSDPA ExpressCard */
        { } /* Terminating entry */
 };
 MODULE_DEVICE_TABLE(usb, option_ids);
@@ -238,6 +251,7 @@ struct option_port_private {
        /* Output endpoints and buffer for this port */
        struct urb *out_urbs[N_OUT_URB];
        char out_buffer[N_OUT_URB][OUT_BUFLEN];
+       unsigned long out_busy;         /* Bit vector of URBs in use */
 
        /* Settings for the port */
        int rts_state;  /* Handshaking pins (outputs) */
@@ -300,7 +314,8 @@ static void option_set_termios(struct usb_serial_port *port,
                        struct ktermios *old_termios)
 {
        dbg("%s", __FUNCTION__);
-
+       /* Doesn't support option setting */
+       tty_termios_copy_hw(port->tty->termios, old_termios);
        option_send_setup(port);
 }
 
@@ -368,7 +383,7 @@ static int option_write(struct usb_serial_port *port,
                        todo = OUT_BUFLEN;
 
                this_urb = portdata->out_urbs[i];
-               if (this_urb->status == -EINPROGRESS) {
+               if (test_and_set_bit(i, &portdata->out_busy)) {
                        if (time_before(jiffies,
                                        portdata->tx_start_time[i] + 10 * HZ))
                                continue;
@@ -392,6 +407,7 @@ static int option_write(struct usb_serial_port *port,
                        dbg("usb_submit_urb %p (write bulk) failed "
                                "(%d, has %d)", this_urb,
                                err, this_urb->status);
+                       clear_bit(i, &portdata->out_busy);
                        continue;
                }
                portdata->tx_start_time[i] = jiffies;
@@ -411,15 +427,16 @@ static void option_indat_callback(struct urb *urb)
        struct usb_serial_port *port;
        struct tty_struct *tty;
        unsigned char *data = urb->transfer_buffer;
+       int status = urb->status;
 
        dbg("%s: %p", __FUNCTION__, urb);
 
        endpoint = usb_pipeendpoint(urb->pipe);
        port = (struct usb_serial_port *) urb->context;
 
-       if (urb->status) {
+       if (status) {
                dbg("%s: nonzero status: %d on endpoint %02x.",
-                   __FUNCTION__, urb->status, endpoint);
+                   __FUNCTION__, status, endpoint);
        } else {
                tty = port->tty;
                if (urb->actual_length) {
@@ -431,7 +448,7 @@ static void option_indat_callback(struct urb *urb)
                }
 
                /* Resubmit urb so we continue receiving */
-               if (port->open_count && urb->status != -ESHUTDOWN) {
+               if (port->open_count && status != -ESHUTDOWN) {
                        err = usb_submit_urb(urb, GFP_ATOMIC);
                        if (err)
                                printk(KERN_ERR "%s: resubmit read urb failed. "
@@ -444,17 +461,29 @@ static void option_indat_callback(struct urb *urb)
 static void option_outdat_callback(struct urb *urb)
 {
        struct usb_serial_port *port;
+       struct option_port_private *portdata;
+       int i;
 
        dbg("%s", __FUNCTION__);
 
        port = (struct usb_serial_port *) urb->context;
 
        usb_serial_port_softint(port);
+
+       portdata = usb_get_serial_port_data(port);
+       for (i = 0; i < N_OUT_URB; ++i) {
+               if (portdata->out_urbs[i] == urb) {
+                       smp_mb__before_clear_bit();
+                       clear_bit(i, &portdata->out_busy);
+                       break;
+               }
+       }
 }
 
 static void option_instat_callback(struct urb *urb)
 {
        int err;
+       int status = urb->status;
        struct usb_serial_port *port = (struct usb_serial_port *) urb->context;
        struct option_port_private *portdata = usb_get_serial_port_data(port);
        struct usb_serial *serial = port->serial;
@@ -462,7 +491,7 @@ static void option_instat_callback(struct urb *urb)
        dbg("%s", __FUNCTION__);
        dbg("%s: urb %p port %p has data %p", __FUNCTION__,urb,port,portdata);
 
-       if (urb->status == 0) {
+       if (status == 0) {
                struct usb_ctrlrequest *req_pkt =
                                (struct usb_ctrlrequest *)urb->transfer_buffer;
 
@@ -493,10 +522,10 @@ static void option_instat_callback(struct urb *urb)
                                req_pkt->bRequestType,req_pkt->bRequest);
                }
        } else
-               dbg("%s: error %d", __FUNCTION__, urb->status);
+               dbg("%s: error %d", __FUNCTION__, status);
 
        /* Resubmit urb so we continue receiving IRQ data */
-       if (urb->status != -ESHUTDOWN) {
+       if (status != -ESHUTDOWN) {
                urb->dev = serial->dev;
                err = usb_submit_urb(urb, GFP_ATOMIC);
                if (err)
@@ -516,7 +545,7 @@ static int option_write_room(struct usb_serial_port *port)
 
        for (i=0; i < N_OUT_URB; i++) {
                this_urb = portdata->out_urbs[i];
-               if (this_urb && this_urb->status != -EINPROGRESS)
+               if (this_urb && !test_bit(i, &portdata->out_busy))
                        data_len += OUT_BUFLEN;
        }
 
@@ -535,7 +564,7 @@ static int option_chars_in_buffer(struct usb_serial_port *port)
 
        for (i=0; i < N_OUT_URB; i++) {
                this_urb = portdata->out_urbs[i];
-               if (this_urb && this_urb->status == -EINPROGRESS)
+               if (this_urb && test_bit(i, &portdata->out_busy))
                        data_len += this_urb->transfer_buffer_length;
        }
        dbg("%s: %d", __FUNCTION__, data_len);
@@ -612,7 +641,10 @@ static void option_close(struct usb_serial_port *port, struct file *filp)
        portdata->dtr_state = 0;
 
        if (serial->dev) {
-               option_send_setup(port);
+               mutex_lock(&serial->disc_mutex);
+               if (!serial->disconnected)
+                       option_send_setup(port);
+               mutex_unlock(&serial->disc_mutex);
 
                /* Stop reading/writing urbs */
                for (i = 0; i < N_IN_URB; i++)