diff options
author | Jarkko Huijts <jarkko.huijts@gmail.com> | 2012-10-10 09:05:06 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-10-25 12:42:26 -0400 |
commit | 6f602912c9d0c84c2edbd446dd9f72660b701605 (patch) | |
tree | 55f34e6658cab9f17f845ec9255a76e25f42ee9f /drivers/usb/serial | |
parent | bd066eef1aea5dd1f8e98934c4c6d21c5e0439c8 (diff) |
usb: serial: ftdi_sio: Add missing chars_in_buffer function
The driver does not wait until the hardware buffer (for data from the PC to the
UART line) is drained when tcdrain or close is called in an application.
Solution: Implement a chars_in_buffer function that checks both the software
and hardware buffer. If the TEMT (TX empty) bit of the line status register
indicates the hw buffer is not empty, let the function return at least 1. This
has been verified to work correctly with an FT232RL. The check on the hw buffer
can not be done for the original SIO device.
Signed-off-by: Jarkko Huijts <jarkko.huijts@gmail.com>
Acked-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/serial')
-rw-r--r-- | drivers/usb/serial/ftdi_sio.c | 60 |
1 files changed, 60 insertions, 0 deletions
diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index be845873e23d..381515572235 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c | |||
@@ -923,6 +923,7 @@ static int ftdi_get_icount(struct tty_struct *tty, | |||
923 | static int ftdi_ioctl(struct tty_struct *tty, | 923 | static int ftdi_ioctl(struct tty_struct *tty, |
924 | unsigned int cmd, unsigned long arg); | 924 | unsigned int cmd, unsigned long arg); |
925 | static void ftdi_break_ctl(struct tty_struct *tty, int break_state); | 925 | static void ftdi_break_ctl(struct tty_struct *tty, int break_state); |
926 | static int ftdi_chars_in_buffer(struct tty_struct *tty); | ||
926 | 927 | ||
927 | static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base); | 928 | static unsigned short int ftdi_232am_baud_base_to_divisor(int baud, int base); |
928 | static unsigned short int ftdi_232am_baud_to_divisor(int baud); | 929 | static unsigned short int ftdi_232am_baud_to_divisor(int baud); |
@@ -957,6 +958,7 @@ static struct usb_serial_driver ftdi_sio_device = { | |||
957 | .ioctl = ftdi_ioctl, | 958 | .ioctl = ftdi_ioctl, |
958 | .set_termios = ftdi_set_termios, | 959 | .set_termios = ftdi_set_termios, |
959 | .break_ctl = ftdi_break_ctl, | 960 | .break_ctl = ftdi_break_ctl, |
961 | .chars_in_buffer = ftdi_chars_in_buffer, | ||
960 | }; | 962 | }; |
961 | 963 | ||
962 | static struct usb_serial_driver * const serial_drivers[] = { | 964 | static struct usb_serial_driver * const serial_drivers[] = { |
@@ -2089,6 +2091,64 @@ static void ftdi_break_ctl(struct tty_struct *tty, int break_state) | |||
2089 | 2091 | ||
2090 | } | 2092 | } |
2091 | 2093 | ||
2094 | static int ftdi_chars_in_buffer(struct tty_struct *tty) | ||
2095 | { | ||
2096 | struct usb_serial_port *port = tty->driver_data; | ||
2097 | struct ftdi_private *priv = usb_get_serial_port_data(port); | ||
2098 | unsigned long flags; | ||
2099 | int chars; | ||
2100 | unsigned char *buf; | ||
2101 | int ret; | ||
2102 | |||
2103 | /* Check software buffer (code from | ||
2104 | * usb_serial_generic_chars_in_buffer()) */ | ||
2105 | spin_lock_irqsave(&port->lock, flags); | ||
2106 | chars = kfifo_len(&port->write_fifo) + port->tx_bytes; | ||
2107 | spin_unlock_irqrestore(&port->lock, flags); | ||
2108 | |||
2109 | /* Check hardware buffer */ | ||
2110 | switch (priv->chip_type) { | ||
2111 | case FT8U232AM: | ||
2112 | case FT232BM: | ||
2113 | case FT2232C: | ||
2114 | case FT232RL: | ||
2115 | case FT2232H: | ||
2116 | case FT4232H: | ||
2117 | case FT232H: | ||
2118 | case FTX: | ||
2119 | break; | ||
2120 | case SIO: | ||
2121 | default: | ||
2122 | return chars; | ||
2123 | } | ||
2124 | |||
2125 | buf = kmalloc(2, GFP_KERNEL); | ||
2126 | if (!buf) { | ||
2127 | dev_err(&port->dev, "kmalloc failed"); | ||
2128 | return chars; | ||
2129 | } | ||
2130 | |||
2131 | ret = usb_control_msg(port->serial->dev, | ||
2132 | usb_rcvctrlpipe(port->serial->dev, 0), | ||
2133 | FTDI_SIO_GET_MODEM_STATUS_REQUEST, | ||
2134 | FTDI_SIO_GET_MODEM_STATUS_REQUEST_TYPE, | ||
2135 | 0, priv->interface, | ||
2136 | buf, 2, WDR_TIMEOUT); | ||
2137 | |||
2138 | if (ret < 2) { | ||
2139 | dev_err(&port->dev, "Unable to read modem and line status: " | ||
2140 | "%i\n", ret); | ||
2141 | goto chars_in_buffer_out; | ||
2142 | } | ||
2143 | |||
2144 | if (!(buf[1] & FTDI_RS_TEMT)) | ||
2145 | chars++; | ||
2146 | |||
2147 | chars_in_buffer_out: | ||
2148 | kfree(buf); | ||
2149 | return chars; | ||
2150 | } | ||
2151 | |||
2092 | /* old_termios contains the original termios settings and tty->termios contains | 2152 | /* old_termios contains the original termios settings and tty->termios contains |
2093 | * the new setting to be used | 2153 | * the new setting to be used |
2094 | * WARNING: set_termios calls this with old_termios in kernel space | 2154 | * WARNING: set_termios calls this with old_termios in kernel space |