dcsimg

More on Mouse Drivers

Last month I walked through the steps of writing a serviceable mouse driver that contained a couple of bugs and that would not work with asynchronous I/O. In this month's column I'll smooth things out a bit. But first, I think we should take a look at some bugs.

Last month I walked through the steps of writing a
serviceable mouse driver that contained a
couple of bugs and that would not work with asynchronous I/O. In this month’s column I’ll smooth things
out a bit. But first, I think we should take a look at some bugs.

The most obvious one isn’t really a driver bug but a failure to
consider certain cases. Imagine that you accidentally bumped this mouse
and sent it skittering across your desk. The mouse_read routine
in our driver would add up all that movement and report it in steps of
±127 until it reported the entire movement. Clearly, there is a
point beyond which mouse movement isn’t really worth reporting, so we’re
going to need to add this as a limit to the interrupt handler
(Listing One).

By adding these checks we limit the range of accumulated movement to
something sensible.

The Subtle Bug

The second bug is a bit more subtle, and that is perhaps why this is
such a common mistake when writing kernel drivers. The mouse_read
routine has a race condition when testing the mouse event flag. Consider
what happens when we execute:



while(!mouse_event)
{




and an interrupt occurs just after this point. The interrupt generates
a mouse event and wakes up the mouse_read routine. However, the following line in mouse_ read is:



interruptible_sleep_on
(&mouse_wait);



which sleeps on the queue, after deciding that no mouse event
has occurred. The result is that we miss the wakeup, and the application
will not see the event until the next mouse interrupt occurs. Although
the consequences will probably be undetectable to the user, this is a
general concern for Linux kernel code and may be much more serious for
other kinds of drivers.







Listing One: Avoiding Information Overload


/* Modified portion of ourmouse_interrupt */
/* … */

if(delta_x || delta_y || new_buttons != mouse_buttons)
{
/* Something happened */

spin_lock(&mouse_lock);
mouse_event = 1;
mouse_dx += delta_x;
mouse_dy += delta_y;

if(mouse_dx < -4096)
mouse_dx = -4096;
if(mouse_dx > 4096)
mouse_dx = 4096;

if(mouse_dy < -4096)
mouse_dy = -4096;
if(mouse_dy > 4096)
mouse_dy = 4096;

mouse_buttons = new_buttons;
spin_unlock(&mouse_lock);

wake_up_interruptible(&mouse_wait);
}
/* … */




There are two ways to solve this race condition. The first is to
disable interrupts during the testing and the call to
interruptible_sleep_on; this works because the sleep causes
interrupts to be re-enabled, and resumption of the process causes them
to be disabled again. Our code thus becomes:



save_flags(flags);
cli();
while(!mouse_event)
{
if(file->f_flags & O_NDELAY)
{
restore_flags(flags);
return -EAGAIN;
}
interruptible_sleep_on(&mouse_wait);
if(signal_pending(current))
{
restore_flags(flags);
return -ERESTARTSYS;
}
}

restore_flags(flags);




This is the sledgehammer approach. It works, but it means we spend more
time turning interrupts on and off. This is also bad for multiprocessor
machines, because disabling interrupts globally is not a simple
operation but requires each processor to be kicked, waiting for each to
disable interrupts and reply.

Deep Magic

The real problem is the race between the event test and the sleep call.
We can avoid this by using the scheduling functions more directly, as
shown in Listing Two.







Listing Two: Avoiding the Race



struct wait_queue wait = { current, NULL };

add_wait_queue(&mouse_wait, &wait);
current->state = TASK_INTERRUPTIBLE;

while(!mouse_event)
{
if(file->f_flags & O_NDELAY)
{
remove_wait_queue(&mouse_wait, &wait);
current->state = TASK_RUNNING;
return -EWOULDBLOCK;
}
if(signal_pending(current))
{
remove_wait_queue(&mouse_wait, &wait);
current->state = TASK_RUNNING;
return -ERESTARTSYS;
}
schedule();
current->state = TASK_INTERRUPTIBLE;
}

remove_wait_queue(&mouse_wait, &wait);
current->state = TASK_RUNNING;




At first sight this probably looks like deep magic. To understand how
this works you need to understand how scheduling and events work on
Linux. Having a good grasp of this is one of the keys to writing clean,
efficient device drivers.

add_wait_queue does what its name suggests; it adds an entry to
a wait queue. The entry in this case is the entry for our current
process. (current is the current task pointer.)

We start by adding an entry for current onto the mouse_
wait
queue. This does not put us to sleep, however: The process is
merely tagged onto the list.

Next, we set the process status to TASK_ INTERRUPTIBLE, which
indicates that the process should not be rescheduled after the next time
it sleeps. Therefore, the process will run until it next sleeps and it
will then need to be woken up explicitly (rather than implicitly by the
scheduler).

The wake_up_interruptible call in the interrupt handler can now
be explained in more detail. This function takes one argument: a wait
queue. It walks down the list of processes on the queue, and any that is
marked as TASK_INTERRUPTIBLE it marks as TASK_RUNNING and
tells the kernel that new processes are runnable.

Behind all the wrappers in the original code what is happening is
this:

1. We add ourselves to the mouse_wait queue.

2. We mark ourselves as sleeping.

3. We ask the kernel to schedule tasks again.

4.The kernel sees we are asleep and schedules some other process.

5. The mouse interrupt sets our state to TASK_RUNNING and makes
a note that the kernel should reschedule tasks.

6. The kernel sees we are running again and continues our
execution.

This is why the magic works. Because we have marked ourselves as
TASK_ INTERRUPTIBLE and have added ourselves to the queue
before we check if there are events pending, the race condition
is removed.

If an interrupt occurs after we check the queue status, but before we
call the schedule() function in order to sleep, things work out.
Instead of missing an event, we are set back to TASK_ RUNNING by
the interrupt handler. We still call schedule(), but it will
continue running our task. We go back around the loop, and this time
there may be an event.

Finally, when we exit the loop we remove ourselves from the
mouse_wait queue, and we set ourselves back to TASK_
RUNNABLE
since we do not wish to go to sleep again just yet.

This isn’t an easy topic. Don’t be afraid to reread the description a
few times and look at other device drivers to see how it works. Finally,
if you can’t grasp it just yet, you can use the code as boilerplate to
write other drivers and trust me instead.

Asynchronous I/O

This leaves the missing feature: asynchronous I/O. Normally Unix
programs use the poll() call (or its variant form,
select()) to wait for an event to occur on one of multiple input
or output devices. This model works well for most tasks, but since
poll() and select() wait until an event occurs, they
aren’t suitable for tasks that must concurrently perform other tasks
(such as computation). Such programs really want the kernel to kick them
when something happens rather than continually wait for events.







Listing Three: Allowing Asynchronous I/O Flags



struct file_operations our_mouse_fops = {
NULL, /* Mice don’t seek */
read_mouse, /* You can read a mouse */
write_mouse, /* This won’t do a lot */
NULL, /* No readdir – not a directory */
poll_mouse, /* Poll */
NULL, /* No ioctl calls */
NULL, /* No mmap */
open_mouse, /* Called on open */
NULL, /* Flush */
close_mouse, /* Called on close */
NULL, /* No media change on a mouse */
fasync_mouse, /* NEW!! Set asynchronous I/O flags */

};



poll() is akin to having a row of lights in front of you. You
can see at a glance which ones, if any, are lit. You cannot, however,
get anything useful done while watching them. Asynchronous I/O uses
signals, which work more like a doorbell. Instead of requiring you to do
nothing but wait around for events, the signal informs you that
something has happened.

Asynchronous I/O sends the signal SIGIO to a user process when
I/O events occur. In our case, this is when the mouse status changes.
When the signal occurs, the user process executes its signal handler
before returning to whatever it was doing previously. It is the
application equivalent of an interrupt handler.







Listing Four: Maintaining a Queue of Handles



static int fasync_mouse(int fd, struct file *filp, int on)
{
int retval = fasync_helper(fd, filp, on, &mouse_fasync);

if (retval < 0)
return retval;
return 0;
}




The kernel provides a simple set of functions for managing asynchronous
I/O. Our first job is to allow users to set the asynchronous I/O flag on
file handles. To do that we need to add a new function to the file
operations table for our mouse (Listing Three).

Once we have installed this entry, the kernel knows we support
asynchronous I/O and will allow all the relevant operations on the
device. Whenever a user adds or removes asynchronous I/O notification on
a file handle, the kernel calls the fasync_mouse routine we just
added. The routine in Listing Four uses helper functions to
maintain a queue of handles for which asynchronous I/O has been
enabled.

fasync_helper adds and deletes entries from a supplied queue of
handles. We also need to remove entries from this list when the file is
closed. This requires that we add one line of code to our close
function:



static int close_mouse(struct inode *inode, struct file *file)
{
fasync_mouse(-1, file, 0)
if(–mouse_users)
return 0;
free_irq(OURMOUSE_IRQ, NULL);
MOD_DEC_USE_COUNT;
return 0;
}



When we close the file we now call our own fasync handler as if
the user had requested that this file cease to be used for asynchronous
I/O. This rather neatly cleans up any loose ends.

At this point the mouse driver supports all the asynchronous I/O
operations, and applications using them will not get an error. However,
we still need to arrange for signals to be delivered to processes using
asynchronous I/O. Again, the kernel provides a function for handling
this. We update our interrupt handler a little:



/* Modified portion of ourmouse_interrupt */
/* … */
mouse_buttons = new_buttons;
spin_unlock(&mouse_lock);
/* Now we do asynchronous I/O */
if(mouse_fasync)
kill_fasync(mouse_fasync, SIGIO);
wake_up_interruptible(&mouse_wait);
/* … */




The new code simply calls the kill_fasync() routine provided by
the kernel if the queue is nonempty. This sends the required signal
(SIGIO in this case) to each process owning a file handle in the
mouse_fasync queue.

With this in place and the bugs in the original version fixed, you now
have a fully functional mouse driver using the bus mouse protocol. It
will work with the X Window System and GPM, and should work with every
other application you need. Doom is, of course, the ideal way to
test that your new mouse driver is functioning properly. Be sure to test
it thoroughly.


Alan Cox is a well-known Linux hacker working on development of
drivers, Linux/SGI porting, and modular sound. He can be reached at
alan@lxorguk.ukuu.org.uk.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62