USB: serial: pl2303: fix NULL-deref at open
[pandora-kernel.git] / drivers / usb / serial / pl2303.c
index e3936c1..c23f716 100644 (file)
@@ -51,6 +51,7 @@ static const struct usb_device_id id_table[] = {
        { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_GPRS) },
        { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_HCR331) },
        { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_MOTOROLA) },
+       { USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_ZTEK) },
        { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID) },
        { USB_DEVICE(IODATA_VENDOR_ID, IODATA_PRODUCT_ID_RSAQ5) },
        { USB_DEVICE(ATEN_VENDOR_ID, ATEN_PRODUCT_ID) },
@@ -66,7 +67,6 @@ static const struct usb_device_id id_table[] = {
        { USB_DEVICE(DCU10_VENDOR_ID, DCU10_PRODUCT_ID) },
        { USB_DEVICE(SITECOM_VENDOR_ID, SITECOM_PRODUCT_ID) },
        { USB_DEVICE(ALCATEL_VENDOR_ID, ALCATEL_PRODUCT_ID) },
-       { USB_DEVICE(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_ID) },
        { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_SX1) },
        { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X65) },
        { USB_DEVICE(SIEMENS_VENDOR_ID, SIEMENS_PRODUCT_ID_X75) },
@@ -86,6 +86,9 @@ static const struct usb_device_id id_table[] = {
        { USB_DEVICE(YCCABLE_VENDOR_ID, YCCABLE_PRODUCT_ID) },
        { USB_DEVICE(SUPERIAL_VENDOR_ID, SUPERIAL_PRODUCT_ID) },
        { USB_DEVICE(HP_VENDOR_ID, HP_LD220_PRODUCT_ID) },
+       { USB_DEVICE(HP_VENDOR_ID, HP_LD960_PRODUCT_ID) },
+       { USB_DEVICE(HP_VENDOR_ID, HP_LCM220_PRODUCT_ID) },
+       { USB_DEVICE(HP_VENDOR_ID, HP_LCM960_PRODUCT_ID) },
        { USB_DEVICE(CRESSI_VENDOR_ID, CRESSI_EDY_PRODUCT_ID) },
        { USB_DEVICE(ZEAGLE_VENDOR_ID, ZEAGLE_N2ITION3_PRODUCT_ID) },
        { USB_DEVICE(SONY_VENDOR_ID, SONY_QN3USB_PRODUCT_ID) },
@@ -153,6 +156,8 @@ struct pl2303_private {
        u8 line_control;
        u8 line_status;
        enum pl2303_type type;
+
+       u8 line_settings[7];
 };
 
 static int pl2303_vendor_read(__u16 value, __u16 index,
@@ -180,10 +185,18 @@ static int pl2303_vendor_write(__u16 value, __u16 index,
 static int pl2303_startup(struct usb_serial *serial)
 {
        struct pl2303_private *priv;
+       unsigned char num_ports = serial->num_ports;
        enum pl2303_type type = type_0;
        unsigned char *buf;
        int i;
 
+       if (serial->num_bulk_in < num_ports ||
+                       serial->num_bulk_out < num_ports ||
+                       serial->num_interrupt_in < num_ports) {
+               dev_err(&serial->interface->dev, "missing endpoints\n");
+               return -ENODEV;
+       }
+
        buf = kmalloc(10, GFP_KERNEL);
        if (buf == NULL)
                return -ENOMEM;
@@ -266,10 +279,6 @@ static void pl2303_set_termios(struct tty_struct *tty,
 
        dbg("%s -  port %d", __func__, port->number);
 
-       /* The PL2303 is reported to lose bytes if you change
-          serial settings even to the same values as before. Thus
-          we actually need to filter in this specific case */
-
        if (old_termios && !tty_termios_hw_change(tty->termios, old_termios))
                return;
 
@@ -407,10 +416,29 @@ static void pl2303_set_termios(struct tty_struct *tty,
                dbg("%s - parity = none", __func__);
        }
 
-       i = usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
-                           SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE,
-                           0, 0, buf, 7, 100);
-       dbg("0x21:0x20:0:0  %d", i);
+       /*
+        * Some PL2303 are known to lose bytes if you change serial settings
+        * even to the same values as before. Thus we actually need to filter
+        * in this specific case.
+        *
+        * Note that the tty_termios_hw_change check above is not sufficient
+        * as a previously requested baud rate may differ from the one
+        * actually used (and stored in old_termios).
+        *
+        * NOTE: No additional locking needed for line_settings as it is
+        *       only used in set_termios, which is serialised against itself.
+        */
+       if (!old_termios || memcmp(buf, priv->line_settings, 7)) {
+               i = usb_control_msg(serial->dev,
+                                   usb_sndctrlpipe(serial->dev, 0),
+                                   SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE,
+                                   0, 0, buf, 7, 100);
+
+               dbg("0x21:0x20:0:0  %d", i);
+
+               if (i == 7)
+                       memcpy(priv->line_settings, buf, 7);
+       }
 
        /* change control lines if we are switching to or from B0 */
        spin_lock_irqsave(&priv->lock, flags);