Olaf --
Olaf Hering wrote:
Hmm, I'm not sure how to translate that to LANG=diff
Here is one such translation--a patch to 2.6.7 to add a circular buffer to the pl2303 USB serial driver.
I tested it briefly and it solved the dropped newline problem--I used agetty to test it as you described in your earlier email.
The problem (to explain a bit more) is that the pl2303 driver advertises 64 bytes of space available in its write room function. However, the pl2303 driver must write the 64 bytes in one write call--a second write call following too soon after the first, even if the total bytes written is less than 64, will fail because there is only one urb and no buffer, and that urb is still in transit from the first write, so there is no place to put the data from the second write. The pl2303 driver write function returns 0 in this case, meaning try again later.
However, the n_tty.c serial line discipline code expects that if write room says there is room for at least two bytes, then a write of a CR followed by a write of a NL should both succeed, and it does not check for an error or retry the write if it fails. Since the pl2303 driver fails the second write, the NL is lost.
-- Al
diff --exclude-from=diff.exclude -ur linux-2.6.7/drivers/usb/serial/pl2303.c
linux-2.6.7-ion/drivers/usb/serial/pl2303.c
--- linux-2.6.7/drivers/usb/serial/pl2303.c 2004-06-16 00:19:22.000000000 -0500
+++ linux-2.6.7-ion/drivers/usb/serial/pl2303.c 2004-07-12 14:26:31.231852368 -0500
@@ -67,6 +67,19 @@
+#define PL2303_BUF_SIZE 4096
+#define PL2303_TMP_BUF_SIZE 4096
+
+static char pl2303_tmp_buf[PL2303_TMP_BUF_SIZE];
+static DECLARE_MUTEX(pl2303_tmp_buf_sem);
+
+struct pl2303_buf {
+ unsigned int buf_size;
+ char *buf_buf;
+ char *buf_get;
+ char *buf_put;
+};
+
static struct usb_device_id id_table [] = {
{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID) },
{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID_RSAQ2) },
@@ -140,12 +153,23 @@
static void pl2303_write_bulk_callback (struct urb *urb, struct pt_regs *regs);
static int pl2303_write (struct usb_serial_port *port, int from_user,
const unsigned char *buf, int count);
+static void pl2303_send (struct usb_serial_port *port);
+static int pl2303_write_room(struct usb_serial_port *port);
+static int pl2303_chars_in_buffer(struct usb_serial_port *port);
static void pl2303_break_ctl(struct usb_serial_port *port,int break_state);
static int pl2303_tiocmget (struct usb_serial_port *port, struct file *file);
static int pl2303_tiocmset (struct usb_serial_port *port, struct file *file,
unsigned int set, unsigned int clear);
static int pl2303_startup (struct usb_serial *serial);
static void pl2303_shutdown (struct usb_serial *serial);
+static struct pl2303_buf *pl2303_buf_alloc(unsigned int size);
+static void pl2303_buf_free(struct pl2303_buf *eb);
+static unsigned int pl2303_buf_data_avail(struct pl2303_buf *eb);
+static unsigned int pl2303_buf_space_avail(struct pl2303_buf *eb);
+static unsigned int pl2303_buf_put(struct pl2303_buf *eb, const char *buf,
+ unsigned int count);
+static unsigned int pl2303_buf_get(struct pl2303_buf *eb, char *buf,
+ unsigned int count);
/* All of the device info needed for the PL2303 SIO serial converter */
@@ -168,12 +192,16 @@
.read_bulk_callback = pl2303_read_bulk_callback,
.read_int_callback = pl2303_read_int_callback,
.write_bulk_callback = pl2303_write_bulk_callback,
+ .write_room = pl2303_write_room,
+ .chars_in_buffer = pl2303_chars_in_buffer,
.attach = pl2303_startup,
.shutdown = pl2303_shutdown,
};
struct pl2303_private {
spinlock_t lock;
+ struct pl2303_buf *buf;
+ int write_urb_in_use;
wait_queue_head_t delta_msr_wait;
u8 line_control;
u8 line_status;
@@ -189,13 +217,27 @@
for (i = 0; i < serial->num_ports; ++i) {
priv = kmalloc (sizeof (struct pl2303_private), GFP_KERNEL);
if (!priv)
- return -ENOMEM;
+ goto cleanup;
memset (priv, 0x00, sizeof (struct pl2303_private));
spin_lock_init(&priv->lock);
+ priv->buf = pl2303_buf_alloc(PL2303_BUF_SIZE);
+ if (priv->buf == NULL) {
+ kfree(priv);
+ goto cleanup;
+ }
init_waitqueue_head(&priv->delta_msr_wait);
usb_set_serial_port_data(serial->port[i], priv);
}
return 0;
+
+cleanup:
+ for (--i; i>=0; --i) {
+ priv = usb_get_serial_port_data(serial->port[i]);
+ pl2303_buf_free(priv->buf);
+ kfree(priv);
+ usb_set_serial_port_data(serial->port[i], NULL);
+ }
+ return -ENOMEM;
}
static int set_control_lines (struct usb_device *dev, u8 value)
@@ -211,40 +253,111 @@
static int pl2303_write (struct usb_serial_port *port, int from_user, const unsigned
char *buf, int count)
{
- int result;
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
dbg("%s - port %d, %d bytes", __FUNCTION__, port->number, count);
if (!count)
return count;
- if (port->write_urb->status == -EINPROGRESS) {
- dbg("%s - already writing", __FUNCTION__);
- return 0;
- }
-
- count = (count > port->bulk_out_size) ? port->bulk_out_size : count;
if (from_user) {
- if (copy_from_user (port->write_urb->transfer_buffer, buf, count))
+ if (count > PL2303_TMP_BUF_SIZE)
+ count = PL2303_TMP_BUF_SIZE;
+ down(&pl2303_tmp_buf_sem);
+ if (copy_from_user(pl2303_tmp_buf, buf, count)) {
+ up(&pl2303_tmp_buf_sem);
return -EFAULT;
- } else {
- memcpy (port->write_urb->transfer_buffer, buf, count);
+ }
+ buf = pl2303_tmp_buf;
}
-
+
+ spin_lock_irqsave(&priv->lock, flags);
+ count = pl2303_buf_put(priv->buf, buf, count);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (from_user)
+ up(&pl2303_tmp_buf_sem);
+
+ pl2303_send(port);
+
+ return count;
+}
+
+static void pl2303_send(struct usb_serial_port *port)
+{
+ int count, result;
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ unsigned long flags;
+
+ dbg("%s - port %d", __FUNCTION__, port->number);
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (priv->write_urb_in_use) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return;
+ }
+
+ count = pl2303_buf_get(priv->buf, port->write_urb->transfer_buffer,
+ port->bulk_out_size);
+
+ if (count == 0) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return;
+ }
+
+ priv->write_urb_in_use = 1;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
usb_serial_debug_data (__FILE__, __FUNCTION__, count,
port->write_urb->transfer_buffer);
port->write_urb->transfer_buffer_length = count;
port->write_urb->dev = port->serial->dev;
result = usb_submit_urb (port->write_urb, GFP_ATOMIC);
- if (result)
+ if (result) {
dev_err(&port->dev, "%s - failed submitting write urb, error %d\n",
__FUNCTION__, result);
- else
- result = count;
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->write_urb_in_use = 0;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ // TODO: reschedule pl2303_send
+ }
- return result;
+ schedule_work(&port->work);
+}
+
+static int pl2303_write_room(struct usb_serial_port *port)
+{
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ int room = 0;
+ unsigned long flags;
+
+ dbg("%s - port %d", __FUNCTION__, port->number);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ room = pl2303_buf_space_avail(priv->buf);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ dbg("%s - returns %d", __FUNCTION__, room);
+ return room;
}
+static int pl2303_chars_in_buffer(struct usb_serial_port *port)
+{
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
+ int chars = 0;
+ unsigned long flags;
+
+ dbg("%s - port %d", __FUNCTION__, port->number);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ chars = pl2303_buf_data_avail(priv->buf);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ dbg ("%s - returns %d", __FUNCTION__, chars);
+ return chars;
+}
static void pl2303_set_termios (struct usb_serial_port *port, struct termios
*old_termios)
{
@@ -640,12 +753,17 @@
static void pl2303_shutdown (struct usb_serial *serial)
{
int i;
+ struct pl2303_private *priv;
dbg("%s", __FUNCTION__);
for (i = 0; i < serial->num_ports; ++i) {
- kfree (usb_get_serial_port_data(serial->port[i]));
- usb_set_serial_port_data(serial->port[i], NULL);
+ priv = usb_get_serial_port_data(serial->port[i]);
+ if (priv) {
+ pl2303_buf_free(priv->buf);
+ kfree(priv);
+ usb_set_serial_port_data(serial->port[i], NULL);
+ }
}
}
@@ -783,11 +901,22 @@
static void pl2303_write_bulk_callback (struct urb *urb, struct pt_regs *regs)
{
struct usb_serial_port *port = (struct usb_serial_port *) urb->context;
+ struct pl2303_private *priv = usb_get_serial_port_data(port);
int result;
dbg("%s - port %d", __FUNCTION__, port->number);
-
- if (urb->status) {
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dbg("%s - urb shutting down with status: %d", __FUNCTION__,
urb->status);
+ return;
+ default:
/* error in the urb, so we have to resubmit it */
dbg("%s - Overflow in write", __FUNCTION__);
dbg("%s - nonzero write bulk status received: %d", __FUNCTION__,
urb->status);
@@ -796,14 +925,187 @@
result = usb_submit_urb (port->write_urb, GFP_ATOMIC);
if (result)
dev_err(&urb->dev->dev, "%s - failed resubmitting write urb,
error %d\n", __FUNCTION__, result);
+ else
+ return;
+ }
- return;
+ spin_lock(&priv->lock);
+ priv->write_urb_in_use = 0;
+ spin_unlock(&priv->lock);
+
+ /* send any buffered data */
+ pl2303_send(port);
+}
+
+
+/*
+ * pl2303_buf_alloc
+ *
+ * Allocate a circular buffer and all associated memory.
+ */
+
+static struct pl2303_buf *pl2303_buf_alloc(unsigned int size)
+{
+
+ struct pl2303_buf *eb;
+
+
+ if (size == 0)
+ return NULL;
+
+ eb = (struct pl2303_buf *)kmalloc(sizeof(struct pl2303_buf), GFP_KERNEL);
+ if (eb == NULL)
+ return NULL;
+
+ eb->buf_buf = kmalloc(size, GFP_KERNEL);
+ if (eb->buf_buf == NULL) {
+ kfree(eb);
+ return NULL;
}
- schedule_work(&port->work);
+ eb->buf_size = size;
+ eb->buf_get = eb->buf_put = eb->buf_buf;
+
+ return eb;
+
+}
+
+
+/*
+ * pl2303_buf_free
+ *
+ * Free the buffer and all associated memory.
+ */
+
+void pl2303_buf_free(struct pl2303_buf *eb)
+{
+ if (eb != NULL) {
+ if (eb->buf_buf != NULL)
+ kfree(eb->buf_buf);
+ kfree(eb);
+ }
+}
+
+
+/*
+ * pl2303_buf_data_avail
+ *
+ * Return the number of bytes of data available in the circular
+ * buffer.
+ */
+
+static unsigned int pl2303_buf_data_avail(struct pl2303_buf *eb)
+{
+ if (eb != NULL)
+ return ((eb->buf_size + eb->buf_put - eb->buf_get) % eb->buf_size);
+ else
+ return 0;
}
+/*
+ * pl2303_buf_space_avail
+ *
+ * Return the number of bytes of space available in the circular
+ * buffer.
+ */
+
+static unsigned int pl2303_buf_space_avail(struct pl2303_buf *eb)
+{
+ if (eb != NULL)
+ return ((eb->buf_size + eb->buf_get - eb->buf_put - 1) % eb->buf_size);
+ else
+ return 0;
+}
+
+
+/*
+ * pl2303_buf_put
+ *
+ * Copy data data from a user buffer and put it into the circular buffer.
+ * Restrict to the amount of space available.
+ *
+ * Return the number of bytes copied.
+ */
+
+static unsigned int pl2303_buf_put(struct pl2303_buf *eb, const char *buf,
+ unsigned int count)
+{
+
+ unsigned int len;
+
+
+ if (eb == NULL)
+ return 0;
+
+ len = pl2303_buf_space_avail(eb);
+ if (count > len)
+ count = len;
+
+ if (count == 0)
+ return 0;
+
+ len = eb->buf_buf + eb->buf_size - eb->buf_put;
+ if (count > len) {
+ memcpy(eb->buf_put, buf, len);
+ memcpy(eb->buf_buf, buf+len, count - len);
+ eb->buf_put = eb->buf_buf + count - len;
+ } else {
+ memcpy(eb->buf_put, buf, count);
+ if (count < len)
+ eb->buf_put += count;
+ else /* count == len */
+ eb->buf_put = eb->buf_buf;
+ }
+
+ return count;
+
+}
+
+
+/*
+ * pl2303_buf_get
+ *
+ * Get data from the circular buffer and copy to the given buffer.
+ * Restrict to the amount of data available.
+ *
+ * Return the number of bytes copied.
+ */
+
+static unsigned int pl2303_buf_get(struct pl2303_buf *eb, char *buf,
+ unsigned int count)
+{
+
+ unsigned int len;
+
+
+ if (eb == NULL)
+ return 0;
+
+ len = pl2303_buf_data_avail(eb);
+ if (count > len)
+ count = len;
+
+ if (count == 0)
+ return 0;
+
+ len = eb->buf_buf + eb->buf_size - eb->buf_get;
+ if (count > len) {
+ memcpy(buf, eb->buf_get, len);
+ memcpy(buf+len, eb->buf_buf, count - len);
+ eb->buf_get = eb->buf_buf + count - len;
+ } else {
+ memcpy(buf, eb->buf_get, count);
+ if (count < len)
+ eb->buf_get += count;
+ else /* count == len */
+ eb->buf_get = eb->buf_buf;
+ }
+
+ return count;
+
+}
+
static int __init pl2303_init (void)
{
int retval;
