Re: Serial port arbitration (UUCP device locking protocol?)
Re: Serial port arbitration (UUCP device locking protocol?)
- Subject: Re: Serial port arbitration (UUCP device locking protocol?)
- From: Godfrey van der Linden <email@hidden>
- Date: Sat, 12 Feb 2005 11:47:00 -0800
G'day Dan,
I'll use this email to write up IOSerialFamily arbitration then I'll
add a man page, probably ioss(4).
The traditional UNIX arbitration has some serious limitations. One of
which is that it is very difficult to have a port open waiting for a
fax or data connection and then allow the computer to dial out.
Traditionally this problem was solved with lock files that often
contain PIDs. However there are so many different types and
implementations of the lock file method it is rarely reliable,
especially in an opensource environment.
So when I studied the various different implementations around when
asked to work on the NeXT serial ports I felt that the tty.*/cu.*
mechanism was the cleanest. In those days the modem cable could be
relied upon to correctly wire the carrier detect line. The semantic
was clean that is the getty could open the tty.* line and it would
block until carrier was raised. While the getty was blocked in open
the cu.* line could be opened arbitrarily. The worked very well.
The problem came with fax modems which do not raise the carrier and
Mac's which don't usually wire carrier into their serial cables. When
this happens the enitire NeXT/BSD arbitration model breaks down. Yet I
still liked the basic model. A couple of years ago (yes it has taken a
long time to document this hasn't it, my only excuse is that I have
been busy ;-) I had a hard look at serial arbitration and did a bit of
comparative research and it seemed that the state of the art was to use
UUCP style lock files. I just hated that solution. So I chose to stick
with the basic design of the NeXT/BSD model but implement the concept
of preemptable ports.
One other thing before I go into the details. Traditionally UNIX will
allow multiple clients to open the same serial port. It provided the
TIOCEXCL flag as a mechanism to fail future opens of the same port but
TIOCEXCL had the problem of not being atomic. Hence it was possible
for 2 processes to successfully open the port then both attempt to
promote it to exclusive access, they would both succeed. I changed the
implementation of TIOCEXCL to provide for a true atomic test. If one
process attempts to lock exclusively and another process has beaten it
then the ioctl will fail.
Finally there had to be a mechanism to work out whether an exclusive
port became idle. I couldn't use a blocking open() statement as it was
supposed to return failure if the port was set to be exclusive. And I
didn't like the idea of creating another minor device to exclusively
for blocking a port and I obviously couldn't use an ioctl (you need an
open port to do that). Being an IOKit architect I decided to use the
IOKit model to control this behaviour. To the end I have created a new
"property" called waitForIdle that will block, interruptibly, till the
serial port goes idle, i.e. neither the cu.* nor the tty.* line is
currently open.
So putting all of this together to reliably open a serial port on
MacOSX use the following procedure, note there are still race
conditions involved but you can be reasonably certain that you have a
port reliably.
To open a dialin device (getty/efax), NB this is code I have written of
the top of my head and hasn't been compiled, I make no promises that it
works as written you may need to fix minor errors in spelling.
#include <IOKit/serial/ioss.h>
#include <IOKit/serial/IOSerialKeys.h>
// iterate over I/O registry to find desired node and keep your
// registry entry node (See DTS docs for a write up on how to do
this)
io_registry_entry_t portre = <do the search>
int fd;
int preempt;
tryAgain:
for (;;) {
do {
fd = open(<tty.dialindevice>, O_NONBLOCK);
if (-1 == fd)
if (EBUSY == errno)
continue;
else
exit(1); // Fatal error
// clear O_NONBLOCK flag
if (-1 == fcntl(fd, F_SETFL, 0))
exit(1); // fatal error
if (-1 == ioctl(fd, TIOCEXCL, 0))
if (EBUSY == errno) {
close(fd);
fd = -1;
continue;
}
else
exit(1); // Fatal error
} while(false);
if (-1 != fd)
break;
// We must have had an EBUSY error to be here so wait for idle
if (kIOReturnSuccess != IORegistryEntrySetProperty(portre,
CFSTR(kIOTTYWaitForIdleKey), kCFBooleanTrue)
exit(1); // Fatal error
};
// At this point you have the port exclusively and non-preemtibly
// so now is a good time to program up the modem or do any other
// equipment initialisation you desire.
preempt = 0;`
if (-1 == ioctl(fd, IOSSPREEMPT, &preempt))
exit(1); // Fatal error
// Now the port is in the preemptible - blocking state
// time to issue a blocking read or a select as needed
// waiting for whatever event you are want.
if (-1 == read(fd, buf, sizeof(buf)) // Pseudo code
if (EIO == errno) {
close(fd); // We have been preempted.
goto tryAgain;
}
else
exit(1); // probably fatal
// We have a connection and it is time to say we are no longer
// preemptible
preempt = 1;
if (-1 == ioctl(fd, IOSSPREEMPT, &preempt))
exit(1); // Fatal error
// That's it while we are running non-preemtibly/exclusive further
// attempts to open the port on either cu.* or tty.* will fail with
EBUSY
That is about it, I should note that the callout line is much simpler
but if it gets EBUSY returned from the open it should also spin around
kIOTTYWaitForIdleKey property.
Hope this helps
Godfrey van der Linden
IOSerialFamily (for my sins)
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Darwin-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden