Subject: Time to fix a 25 year old misdesign
To: None <tech-kern@netbsd.org>
From: Lennart Augustsson <lennart@augustsson.net>
List: tech-kern
Date: 10/15/2000 12:12:28
The old UNIX device driver interface is fundamentally broken.  The
problem is the following

   The driver routine open() is called every time the device
   is opened, but close() is only called on the last close.

This principle makes some drivers a little easier to write (I remember
thinking that this was the right way when I first saw it exactly 20
years ago), but it makes implementing certain functionality
impossible.

Example:

A while ago someone pointed out that the audio driver had the
exclusive open property, i.e., after being opened it has to be closed
before it can be opened again.  This is a must for for a device like
audio, since we don't want two different processes to be able to open
it for writing.  But the audio device can actually be viewed as two
devices: one for playback and one for recording.  And his particular
application (an Internet phone program, as I recall it) wanted to open
it separately for reading and writing.  It makes perfect sense,
especially if the two data directions are handled by two different
processes.

So I thought, sure, it should be easy to fix the audio driver to allow
separate open() for read and write.  It's not, it's impossible.
Actually, it's possible, but it won't function right.

Here's how I'd like to implement it:

audio_open()
 if opening for reading
  bail out if already open for reading
  mark as open for reading
 if opening for writing
  bail out if already open for writing
  mark as open for writing
 ...

audio_close()
 if closing for reading
  mark as not open for reading
 if closing for writing
  mark as not open for writing
 ...

This is easy to write (open() and close() are called with the open
mode as an argument), but it is WRONG!  Since audio_close() is only
called on the last close() of the driver only one half will be marked
as closed, the other will forever remain marked as open.

OK, let's try and rewrite the close() routine:

audio_close()
 mark as not open for reading
 mark as not open for writing
 ...

Now the last close() will properly mark both halves as closed.  But it
is still WRONG!  Consider the following sequence of events in a
userland process:

 open(audio, READ);
 open(audio, WRITE);
 ...
 close(audio, WRITE);
 ...
 open(audio, WRITE);
 ...

The last open() in this sequence will fail and claim that the device
is busy.  Why?  Because the last close of the device has not been
done, so it is not marked as unopen for writing.

I can see no way of solving this with the current device driver
structure.  The only way out would be to have separate devices nodes
for reading and writing, but that is not what people nor programs
expect.


Is this problem an isolated thing?  No, the exact same problem exists
for other devices too.
Example: the ugen device and its endpoint devices.
Example: the wskbd/wsmouse/wsmux devices where you'd like to be able
to open a device for reading, and also open it for ioctl().


How should it be fixed?  Well, the only% way I can see is to change
when the close() routine is called for character device drivers.
Doing that would be a huge amount of work, so instead I suggest that
we add a routine to the struct cdevsw.  This routine will have the
same type as the close() routine, but will be called on every close().
If left out, it will not be called.  This way all old drivers can
remain intact, and only those that require the new functionality need
to know about it.

Comments, please.

If it is generally agreed that this is a good idea, I'd be willing
to do the work.
If you don't think it's a good idea, then please tell me how to
solve my problem instead.

 -- Lennart


% Perhaps the problem could be solved with some cloning device
as well.



--

        -- Lennart