-
Notifications
You must be signed in to change notification settings - Fork 159
Description
When testing low baudrates, the program will appear to hang and require hitting ^C to exit. For example, try:
./linux-serial-test -s -p /dev/ttyUSB0 -o 2 -i 2 -b 1200
In particular the hang is in the close(_fd) call in theexit_handler() because Linux waits up to 30 seconds by default due to its closing_wait setting. Using tcflush() does not help.
In Linux, one can use the setserial command to change the maximum closing wait delay from 30 seconds to "none". In C, one would set ASYNC_CLOSING_WAIT_NONE via the TIOCSSERIAL ioctl.
Here is an example of how I did it in my fork:
static int disable_closing_wait()
{
// Disable Linux's 30 second wait for close() at slow baudrates.
// (For no apparent reason, Linux requires root permissions for this.)
unsigned short oldcw = 3000;
struct serial_struct ss;
int eta = 999999999;
// Don't bother trying if it won't take long to drain.
int baud = _e_baud?_e_baud:_cl_baud;
if (baud) {
eta = ( _write_count - _read_count) * bitsperframe() / baud;
}
if (eta <= 2) {
return -1;
}
if (ioctl(_fd, TIOCGSERIAL, &ss) < 0) {
// return silently as some devices do not support TIOCGSERIAL
return -1;
}
oldcw = ss.closing_wait;
if (oldcw == ASYNC_CLOSING_WAIT_NONE) {
return -1;
}
ss.closing_wait = ASYNC_CLOSING_WAIT_NONE;
if (ioctl(_fd, TIOCSSERIAL, &ss) < 0) {
perror("TIOCSSERIAL ASYNC_CLOSING_WAIT_NONE");
fprintf(stderr, "Estimated time to drain: %d seconds", eta);
if (eta > oldcw/100) {
fprintf(stderr, " (closing_wait max is %ds)", oldcw/100);
}
fprintf(stderr, "\n");
return -1;
}
return oldcw;
}
static void reenable_closing_wait(unsigned short oldcw)
{
// Re-open the serial port temporarily and reset closing_wait
struct serial_struct ss;
int fd = open(_cl_port, O_RDWR | O_NONBLOCK);
if (ioctl(fd, TIOCGSERIAL, &ss) >= 0) {
ss.closing_wait = oldcw;
if (ioctl(fd, TIOCSSERIAL, &ss) < 0) {
int ret = -errno;
perror("TIOCSSERIAL reenable closing wait");
exit(ret);
}
}
close(fd);
}
static void close_no_waiting(int fd) {
int oldcw = disable_closing_wait();
close(fd);
if (oldcw >= 0) {
reenable_closing_wait(oldcw);
}
}
static void exit_handler(void)
{
printf("Exit handler: Cleaning up ...\n");
if (_fd >= 0) {
tcflush(_fd, TCIOFLUSH);
close_no_waiting(_fd);
flock(_fd, LOCK_UN);
}
...
etc
...You'll notice that my code only runs if the estimated closing_wait time is longer than two seconds. This is because Linux forbids normal users from changing the closing_wait delay and I do not expect most people will want to run linux-serial-test as root. This way, only the people who suffer from the problem will see messages about not being able to modify the closing_wait period and an estimate for how long they'll have to wait.