Dear Sirs,
the support of the serial interface by linux systems nowadays suffers
from the disadvantage, that hardware handshake is only supported for the
full duplex mode. That means, that the RTS signal is getting always
asserted immediately after the serial was opened. So the RTS signal
under linux, as far as I understood it, carries exactly the same
information as the DTR line. That means, that there is no half-duplex
behaviour provided by linux. A half-duplex application as e.g. RS-485
controllers or radio modems are depending on a different behaviour: The
serial port asserts DTR after it was opened, but RTS must only be
asserted, when data has been written to the tx buffer for transmission.
Stimulated by the RTS signal, the DCE will then engage the channel for
transmission and release it, after the RTS has been deasserted when the
buffer had been sent. This behaviour is up to now not supported by the
linux serial drivers.
Therefore I make a suggestion for adding the half-duplex support for
linux kernels by the attached patch, which I created and tested on a
UART 16550 compliant hardware and additionally by means of a ftdi usb
to serial port adaptor. The patch was compiled and tested with an
UBUNTU 2.6.24-19-generic environment.
If you see a chance, to add this functionality for future linux kernels,
I would be interested in further discussions about this patch.
Regards
Axel Hosemann
Bergstrasse 6f
CH 5644 Auw
diff -urN linux-2.6.24/drivers/serial/serial_core.c linux-2.6.24-new/drivers/serial/serial_core.c
--- linux-2.6.24/drivers/serial/serial_core.c 2008-02-11 06:51:11.000000000 +0100
+++ linux-2.6.24-new/drivers/serial/serial_core.c 2008-07-07 22:36:16.000000000 +0200
@@ -75,6 +75,9 @@
*/
BUG_ON(!info);
tasklet_schedule(&info->tlet);
+#ifdef RTSHDX
+ wake_up_interruptible(&port->rts_wait);
+#endif
}
static void uart_stop(struct tty_struct *tty)
@@ -181,8 +184,15 @@
* Setup the RTS and DTR signals once the
* port is open and ready to respond.
*/
- if (info->tty->termios->c_cflag & CBAUD)
+ if (info->tty->termios->c_cflag & CBAUD){
+#ifdef RTSHDX
+ port->toggleRTS = false;
+ uart_set_mctrl(port, TIOCM_DTR);
+ uart_clear_mctrl(port, TIOCM_RTS);
+#else
uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR);
+#endif
+ }
}
if (info->flags & UIF_CTS_FLOW) {
@@ -473,6 +483,47 @@
uart_start(tty);
}
+#ifdef RTSHDX
+static void uart_wait_infinite_until_sent(struct tty_struct *tty)
+{
+ struct uart_state *state = tty->driver_data;
+ struct uart_port *port = state->port;
+ unsigned long char_time;
+
+ BUG_ON(!kernel_locked());
+
+ char_time = (port->timeout - HZ/50) / port->fifosize;
+ char_time = char_time / 5;
+ if (char_time == 0)
+ char_time = 1;
+
+ if (port->type == PORT_UNKNOWN || port->fifosize == 0)
+ return;
+
+ /*
+ * Check whether the transmitter is empty every 'char_time'.
+ */
+ while (!port->ops->tx_empty(port)) {
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ }
+ //wait again until shift register is empty
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ set_current_state(TASK_RUNNING); /* might not be needed */
+}
+
+static void simplified_sleep_on(wait_queue_head_t *queue)
+{
+ wait_queue_t wait;
+
+ init_waitqueue_entry(&wait, current);
+ current->state = TASK_INTERRUPTIBLE;
+
+ add_wait_queue(queue, &wait);
+ schedule();
+ remove_wait_queue (queue, &wait);
+}
+#endif
+
static int
uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
@@ -497,6 +548,9 @@
if (!circ->buf)
return 0;
+#ifdef RTSHDX
+ uart_set_mctrl(port, TIOCM_RTS);
+#endif
spin_lock_irqsave(&port->lock, flags);
while (1) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
@@ -513,6 +567,16 @@
spin_unlock_irqrestore(&port->lock, flags);
uart_start(tty);
+#ifdef RTSHDX
+ if(port->toggleRTS){
+ //first wait until the device indicates transmission started
+ simplified_sleep_on(&port->rts_wait);
+ //second wait until the device indicates transmission done
+ uart_wait_infinite_until_sent(tty);
+ //now we are ready to deassert RTS
+ uart_clear_mctrl(port, TIOCM_RTS);
+ }
+#endif
return ret;
}
@@ -1143,6 +1207,15 @@
unsigned int cflag = tty->termios->c_cflag;
BUG_ON(!kernel_locked());
+#ifdef RTSHDX
+ state->port->toggleRTS = tty->termios->c_lflag & RTSHDX ? true : false;
+ if(state->port->toggleRTS){
+ uart_clear_mctrl(state->port, TIOCM_RTS);
+ }
+ else {
+ uart_set_mctrl(state->port, TIOCM_RTS);
+ }
+#endif
/*
* These are the bits that are used to setup various
@@ -1167,8 +1240,14 @@
if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
unsigned int mask = TIOCM_DTR;
if (!(cflag & CRTSCTS) ||
- !test_bit(TTY_THROTTLED, &tty->flags))
+ !test_bit(TTY_THROTTLED, &tty->flags)){
+#ifdef RTSHDX
+ if(!state->port->toggleRTS)
+ mask |= TIOCM_RTS;
+#else
mask |= TIOCM_RTS;
+#endif
+ }
uart_set_mctrl(state->port, mask);
}
@@ -1413,8 +1492,15 @@
/*
* And finally enable the RTS and DTR signals.
*/
- if (tty->termios->c_cflag & CBAUD)
+ if (tty->termios->c_cflag & CBAUD){
+#ifdef RTSHDX
+ //uart_update_mctrl(port,TIOCM_DTR,TIOCM_RTS);
+ uart_clear_mctrl(port, TIOCM_RTS);
+ uart_set_mctrl(port, TIOCM_DTR);
+#else
uart_set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+#endif
+ }
}
}
@@ -1527,6 +1613,9 @@
if (state->info) {
init_waitqueue_head(&state->info->open_wait);
init_waitqueue_head(&state->info->delta_msr_wait);
+#ifdef RTSHDX
+ init_waitqueue_head(&state->port->rts_wait);
+#endif
/*
* Link the info into the other structures.
diff -urN linux-2.6.24/drivers/usb/serial/ftdi_sio.c linux-2.6.24-new/drivers/usb/serial/ftdi_sio.c
--- linux-2.6.24/drivers/usb/serial/ftdi_sio.c 2008-02-11 06:51:11.000000000 +0100
+++ linux-2.6.24-new/drivers/usb/serial/ftdi_sio.c 2008-07-07 22:40:30.000000000 +0200
@@ -301,6 +301,12 @@
unsigned long tx_bytes;
unsigned long tx_outstanding_bytes;
unsigned long tx_outstanding_urbs;
+#ifdef RTSHDX
+ unsigned char charLen;
+ bool toggleRTS;
+ char extended_status;
+ wait_queue_head_t rts_wait; // Used for blocking write
+#endif
};
/* struct ftdi_sio_quirk is used by devices requiring special attention. */
@@ -722,6 +728,20 @@
return(ftdi_232bm_baud_base_to_divisor(baud, 48000000));
}
+#ifdef RTSHDX
+static void simplified_sleep_on(wait_queue_head_t *queue)
+{
+ wait_queue_t wait;
+
+ init_waitqueue_entry(&wait, current);
+ current->state = TASK_INTERRUPTIBLE;
+
+ add_wait_queue(queue, &wait);
+ schedule();
+ remove_wait_queue (queue, &wait);
+}
+#endif
+
#define set_mctrl(port, set) update_mctrl((port), (set), 0)
#define clear_mctrl(port, clear) update_mctrl((port), 0, (clear))
@@ -1221,6 +1241,9 @@
spin_lock_init(&priv->rx_lock);
spin_lock_init(&priv->tx_lock);
init_waitqueue_head(&priv->delta_msr_wait);
+#ifdef RTSHDX
+ init_waitqueue_head(&priv->rts_wait);
+#endif
/* This will push the characters through immediately rather
than queue a task to deliver them */
priv->flags = ASYNC_LOW_LATENCY;
@@ -1372,8 +1395,13 @@
/* FIXME: Flow control might be enabled, so it should be checked -
we have no control of defaults! */
/* Turn on RTS and DTR since we are not flow controlling by default */
+#ifdef RTSHDX
+ priv->toggleRTS = false;
+ set_mctrl(port, TIOCM_DTR);
+ clear_mctrl(port, TIOCM_RTS);
+#else
set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
-
+#endif
/* Not throttled */
spin_lock_irqsave(&priv->rx_lock, flags);
priv->rx_flags &= ~(THROTTLED | ACTUALLY_THROTTLED);
@@ -1453,7 +1481,9 @@
int status;
int transfer_size;
unsigned long flags;
-
+#ifdef RTSHDX
+ int baud;
+#endif
dbg("%s port %d, %d bytes", __FUNCTION__, port->number, count);
if (count == 0) {
@@ -1529,6 +1559,9 @@
usb_sndbulkpipe(port->serial->dev, port->bulk_out_endpointAddress),
buffer, transfer_size,
ftdi_write_bulk_callback, port);
+#ifdef RTSHDX
+ set_mctrl(port, TIOCM_RTS);
+#endif
status = usb_submit_urb(urb, GFP_ATOMIC);
if (status) {
@@ -1545,10 +1578,25 @@
/* we are done with this urb, so let the host driver
* really free it when it is finished with it */
usb_free_urb(urb);
+#ifdef RTSHDX
+ if(priv->toggleRTS){
+ priv->extended_status &= (~FTDI_RS_TEMT);
+ while((priv!=NULL)&&(!(priv->extended_status & FTDI_RS_TEMT))){
+ simplified_sleep_on(&priv->rts_wait);
+ }
+ baud = tty_get_baud_rate(port->tty);
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(usecs_to_jiffies((2000000 * priv->charLen) / baud));
+ clear_mctrl(port, TIOCM_RTS);
+ }
+#endif
dbg("%s write returning: %d", __FUNCTION__, count);
return count;
error:
+#ifdef RTSHDX
+ clear_mctrl(port, TIOCM_RTS);
+#endif
usb_free_urb(urb);
error_no_urb:
kfree (buffer);
@@ -1769,12 +1817,22 @@
/* N.B. packet may be processed more than once, but differences
* are only processed once. */
if (priv != NULL) {
+#ifdef RTSHDX
+ char new_ext_status;
+#endif
char new_status = data[packet_offset+0] & FTDI_STATUS_B0_MASK;
if (new_status != priv->prev_status) {
priv->diff_status |= new_status ^ priv->prev_status;
wake_up_interruptible(&priv->delta_msr_wait);
priv->prev_status = new_status;
}
+#ifdef RTSHDX
+ new_ext_status = data[packet_offset+1] & FTDI_RS_TEMT;
+ if ((new_ext_status != (priv->extended_status & FTDI_RS_TEMT)) && (new_ext_status != 0)){
+ priv->extended_status |= new_ext_status;
+ wake_up_interruptible(&priv->rts_wait);
+ }
+#endif
}
length = min(PKTSZ, urb->actual_length-packet_offset)-2;
@@ -1940,6 +1998,9 @@
struct ftdi_private *priv = usb_get_serial_port_data(port);
struct ktermios *termios = port->tty->termios;
unsigned int cflag = termios->c_cflag;
+#ifdef RTSHDX
+ unsigned int lflag = termios->c_lflag;
+#endif
__u16 urb_value; /* will hold the new flags */
char buf[1]; /* Perhaps I should dynamically alloc this? */
@@ -1974,25 +2035,67 @@
/* Set number of data bits, parity, stop bits */
+#ifdef RTSHDX
+ priv->charLen = 1;
+#endif
termios->c_cflag &= ~CMSPAR;
urb_value = 0;
urb_value |= (cflag & CSTOPB ? FTDI_SIO_SET_DATA_STOP_BITS_2 :
FTDI_SIO_SET_DATA_STOP_BITS_1);
+#ifdef RTSHDX
+ priv->charLen += cflag & CSTOPB ? 2 : 1;
+#endif
urb_value |= (cflag & PARENB ?
(cflag & PARODD ? FTDI_SIO_SET_DATA_PARITY_ODD :
FTDI_SIO_SET_DATA_PARITY_EVEN) :
FTDI_SIO_SET_DATA_PARITY_NONE);
- if (cflag & CSIZE) {
- switch (cflag & CSIZE) {
- case CS5: urb_value |= 5; dbg("Setting CS5"); break;
- case CS6: urb_value |= 6; dbg("Setting CS6"); break;
- case CS7: urb_value |= 7; dbg("Setting CS7"); break;
- case CS8: urb_value |= 8; dbg("Setting CS8"); break;
+#ifdef RTSHDX
+ priv->charLen += cflag & PARENB ? 1 : 0;
+#endif
+ if (cflag & CSIZE){
+ switch (cflag & CSIZE){
+ case CS5:
+#ifdef RTSHDX
+ priv->charLen += 5;
+#endif
+ urb_value |= 5;
+ dbg("Setting CS5");
+ break;
+ case CS6:
+#ifdef RTSHDX
+ priv->charLen += 6;
+#endif
+ urb_value |= 6;
+ dbg("Setting CS6");
+ break;
+ case CS7:
+#ifdef RTSHDX
+ priv->charLen += 7;
+#endif
+ urb_value |= 7;
+ dbg("Setting CS7");
+ break;
+ case CS8:
+#ifdef RTSHDX
+ priv->charLen += 8;
+#endif
+ urb_value |= 8;
+ dbg("Setting CS8");
+ break;
default:
err("CSIZE was set but not CS5-CS8");
}
}
+#ifdef RTSHDX
+ if(lflag & RTSHDX){
+ priv->toggleRTS = true;
+ }
+ else{
+ priv->toggleRTS = false;
+ set_mctrl(port, TIOCM_RTS);
+ }
+#endif
/* This is needed by the break command since it uses the same command - but is
* or'ed with this value */
@@ -2025,7 +2128,17 @@
}
/* Ensure RTS and DTR are raised when baudrate changed from 0 */
if (!old_termios || (old_termios->c_cflag & CBAUD) == B0) {
+#ifdef RTSHDX
+ set_mctrl(port, TIOCM_DTR);
+ if(priv->toggleRTS){
+ clear_mctrl(port, TIOCM_RTS);
+ }
+ else{
+ set_mctrl(port, TIOCM_RTS);
+ }
+#else
set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+#endif
}
}
diff -urN linux-2.6.24/include/asm-x86/termbits.h linux-2.6.24-new/include/asm-x86/termbits.h
--- linux-2.6.24/include/asm-x86/termbits.h 2008-02-11 06:51:11.000000000 +0100
+++ linux-2.6.24-new/include/asm-x86/termbits.h 2008-06-15 00:24:00.000000000 +0200
@@ -176,6 +176,7 @@
#define ECHOPRT 0002000
#define ECHOKE 0004000
#define FLUSHO 0010000
+#define RTSHDX 0020000
#define PENDIN 0040000
#define IEXTEN 0100000
diff -urN linux-2.6.24/include/linux/serial_core.h linux-2.6.24-new/include/linux/serial_core.h
--- linux-2.6.24/include/linux/serial_core.h 2008-02-11 06:51:11.000000000 +0100
+++ linux-2.6.24-new/include/linux/serial_core.h 2008-06-16 00:28:06.000000000 +0200
@@ -297,6 +297,10 @@
unsigned char suspended;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
+#ifdef RTSHDX
+ bool toggleRTS;
+ wait_queue_head_t rts_wait; // Used for blocking write
+#endif
};
/*