[uClinux-dev] serial/scheduling weirdness (MCF5206)

Andrew Kohlsmith akohlsmith-uclinux at benshaw.com
Fri Aug 26 10:33:32 EDT 2005

I'm working on a serial protocol gateway and I'm using an old Arnewsh 5206 
eval board I have for the development platform.  Actual hardware will 
probably end up being Dragonball or another Coldfire core but anyway that's 
more or less irrelavent.

I'm trying to control RTS which is connected to my RS485 driver enable.  I'm 
using the standard IOCTLs to do this, and it *is* working, but the scheduling 
latencies are driving me insane.

By default HZ is 100 which gives me a 10ms resolution to any scheduling.  This 
is nowhere near granular enough to accurately control my driver, which must 
be disabled within 3.5 character times of the message ending, as that's what 
the Modbus specification is stating is a minimum turnaround time from my 
request to a device's response.

My userland code is doing this:


 tcflush(fd, TCIOFLUSH);
 c = crc(header, len, -1);
 mb_write(fd, header, len);

/* command 0x10's has other data to follow before the CRC */
 if(cmd == 0x10) {
  mb_write(fd, data, sizeof(char) * num_reg);
  c = crc(data, sizeof(char) * num_reg, c);
/* format CRC to little-endian */
 le_crc[0] = c & 0xff;
 le_crc[1] = c >> 8;

 mb_write(fd, &le_crc, 2);

/* wait for all data to be transmitted before turning off RTS */
 tcflush(fd, TCIFLUSH);


RTSON/OFF are just a pair of small routines which execute a pair of IOCTLS 
(TIOCMGET and TIOCMSET) to flip RTS however which way I need.  Now I know I 
can optimize that a little by keeping state and just executing TIOCMSET but I 
don't think that's the issue here.

I've already written about the bug in mcfserial.c which doesn't wait for the 
transmitter holding and shift registers to drain on anything but the MCF5272 
but I've cleaned that up with this little hack:


 * mcfrs_wait_until_sent() --- wait until the transmitter is empty
static void
mcfrs_wait_until_sent(struct tty_struct *tty, int timeout)
 unsigned int i;
 struct mcf_serial * info = (struct mcf_serial *)tty->driver_data;
 volatile unsigned char *uartp;
 unsigned long orig_jiffies, fifo_time, char_time, fifo_cnt;

 if (serial_paranoia_check(info, tty->name, "mcfrs_wait_until_sent"))

 orig_jiffies = jiffies;

 do {  i++; } while(((uartp[MCFUART_USR] &
  && (jiffies < (orig_jiffies + 1)));

 printk("mcfrs_wus: %i loops\n", i);


Basically it just sits in a tight loop waiting for the (two-byte) FIFO to 
empty.  At 19200 baud that's a maximum of 1ms.  I had originally used 
cpu_idle() in the loop (which evaluates down to a do { } while(0)) but it 
wasn't acting any differently (in fact it seemed to be taking a lot longer) 
so I'm being mean and spinning in the kernel for up to 1ms.  :-)

I have an oscilloscope watching the RS485 line and I am seeing that the driver 
isn't being disabled for around 10ms.  My printk output is showing either 1 
loop (transmitter already empty) or 308 or 512ish loops (1 byte left/two 
bytes left)... so I know that's all working.

I guess my question is this:  short of screwing with the HZ value or trying to 
hack in an RS485 mode to the mcfserial driver, is there any way that I can 
get from

write() -> tc_drain() -> RTSOFF()

as fast as possible, *most* of the time?  I realize that sometimes my 
timeslice will disappear between these three items and I might miss a 
response but I'm not even getting through this fast enough 1% of the time.  
If I don't tc_drain(), or if I use the original mcf_wait_until_sent() which 
just returns right away, I have a much higher success rate (80ish%) -- I'm 
aiming for closer to 100% through better design, but I'm not having much 

This is all on the 2.6 kernel with the 20041215 snapshot.  I have CVS and want 
to move to 2.4 kernel on it but I'm having a lot of issues getting MTD and 
2.4 working nicely so I'm leaving it be for the moment.


More information about the uClinux-dev mailing list