USB: pl2303: fix data corruption on termios updates
[pandora-kernel.git] / drivers / usb / serial / pl2303.c
index e3936c1..7b29317 100644 (file)
@@ -153,6 +153,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,
@@ -266,10 +268,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 +405,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);