I think I have tracked down a problem that has been written about
recently w/r/t the Linux implementation of USB to serial (RS232C)
adapters (including modems attached via USB).  I will preface my
remarks by saying I have, if you'll pardon the pun, peripheral
knowledge of what's going on, but not enough depth of Linux kernel
or USB knowledge to fix it.  This is with 2.4.21.

The problem: when running an interactive session through a USB to
serial device (in my case, a Keyspan USA49WLC), as soon as the getty
hands off control to login (in my case, mgetty running /bin/login), it
sets the ldisc to the usual one, with bits OPOST and ONLCR on.  The
intent is that whenever \n is seen in the output stream, \r\n actually
goes out over the device.  Terminals and emulators of course expect
that "\r" means "go to the leftmost column" and "\n" means "go down
one line."  Neither alone is sufficient for "newline," unless your
emulator is told that one or the other of those symbols performs both
functions (e.g., C-Kermit "set terminal cr-display crlf" coupled w/
setting OPOST|ONLRET).

Examining drivers/char/n_tty.c, one can see the opost() routine which
accomplishes this.  If the tty's modes indicate both OPOST and ONLCR,
and the octet to be output is \n, it checks to see there are 2 octets
available in the device output buffer.  If not, the write() fails.  If
OK, it calls the device driver's put_char with \r, then the generic
part of the routine which outputs the char to be written, or \n in
this case.  If the device driver defines a put_char routine, it is
called, but put_char is not generally defined.  Therefore, the
tty_default_put_char is called.  This is simply a call to the driver's
write() routine with length 1.  Part of the problem is this is a
function returning void.

Now, at least in the Keyspan case, the write() routine has an
INPROGRESS flag.  The generic tty driver dutifully calls the device's
write() with the data up to \n (if any), then writes a \r, then a \n.
(Remember, put_char('\r') put_char('\n') gets turned into two writes
of size 1.)  The first one (of \r) goes through just fine.  The second
one (of \n) however sees the INPROGRESS flag, and dutifully reports
back to its caller that the char has not been written (returns written
count != requested count, basically).  The routine that called it,
serial_write(), also dutifully returns the count written.  But the
problem is that particular return value is discarded because the
funtion that called it returns void.  There's no "retry loop" or

My understanding of USB is that it's packetized, so the lowest level
driver may be arranging to have those characters sent in a packet to
the serial converter/adapter, hence the INPROGRESS.  It would be
waiting for the bh to signal that the host controller has finished
with the buffer sent to it.  So it's a timing issue: if there is
enough time between those requests (as when I used
insmod keyspan debug=1 with the console being a serial port), all is
just fine and the display is "normal."  If not, the opost() writes the
\r, and drops the \n off the face of the Earth because
tty_default_put_char() ignores the fact that request != return.

So what's the most practical solution?  Can serial_write() pause until
INPROGRESS clears?  Should a put_char() be added, and how should it be
implemented?  Should serial_write() just accept the charater and
buffer it somehow, and write it onto the USB at some future time (such
as in the callback routines)?

This sf.net email is sponsored by:ThinkGeek
Welcome to geek heaven.
To unsubscribe, use the last form field at:

Reply via email to