dcsimg

Writing Linux Mouse Drivers

Mice are conceptually one of the simplest device drivers in the Linux operating system. Not all mice are handled by the kernel; rather, there is a two-layer abstraction. The kernel provides services for mice that cannot be driven directly by the user libraries and applications. That is, mice other than serial mice. On top of this library and application, programs (selection or gpm) provide a single common interface to all mice whether supported directly by the kernel or via a serial line.

Mice are conceptually one of the simplest device drivers in the Linux operating system. Not all mice are handled by the kernel; rather, there is a two-layer abstraction. The kernel provides services for mice that cannot be driven directly by the user libraries and applications. That is, mice other than serial mice. On top of this library and application, programs (selection or gpm) provide a single common interface to all mice whether supported directly by the kernel or via a serial line.

gpm — the general purpose mouse driver handles cutting and pasting on the text consoles, and provides a general library for mouse-aware applications. It also handles the sharing of mouse services with the X Windows user interface.

Sometimes a mouse speaks a sufficiently convoluted protocol that the protocol is handled by gpm itself. However, most mice use a common protocol called the bus mouse protocol.

Each read from a bus mouse interface device returns a block of data. The first three bytes of each block are defined as follows:

Byte 0:0×80 + the buttons currently down

Byte 1:A signed value for the shift in X position

Byte 2:A signed value for the shift in Y position

An application can choose to read more than 3 bytes. Some mice send device-specific information in the rest of the block, while in others any additional bytes read will be zero.

The position values are truncated if they exceed the 8-bit range (that is, -127 <= delta <= 127). Since the value -128 does fit into a byte, it is not allowed.

The buttons are numbered left to right as 0, 1, 2, 3, and so forth. Each button sets the corresponding bit in the first byte of the block, with bit 7 always being set. So, if the left and right buttons of a three-button mouse are depressed, the value read back will be 0×85 (10000101 in binary).

All mouse drivers are required to support polling for mouse events, by implementing the poll() operation. Indeed, most applications use polling to wait for mouse events to occur. Mouse drivers also support asynchronous I/O. This is a topic we have not yet covered, but which I will explain after looking at a simple mouse driver.

Initializing

First we will need the initialization functions for our mouse device (Listing 1). To keep this simple, our imaginary mouse device uses three I/O ports fixed at I/O address 0×300-0×302 and always lives at interrupt 5. The ports will be the X position, the Y position, and the buttons in that order. So, conceptually the goal of this driver is very simple: it must respond to mouse events and translate the values read from these three I/O ports into a block of data in the format described above.




Listing 1: Initializing Functions


#define OURMOUSE_BASE   0×300

static struct miscdevice our_mouse = {
OURMOUSE_MINOR, “ourmouse”,
&our_mouse_fops
};

__init ourmouse_init(void)
{

if(check_region(OURMOUSE_BASE, 3))
return -ENODEV;
request_region(OURMOUSE_BASE, 3,
“ourmouse”);

misc_register(&our_mouse);
return 0;
}

The use of misc_register is new here. The idea is to have a single device (the so-called “misc” device) with a single major device number, out of which multiple minor device numbers are allocated. Linux normally parcels out devices by major number, each of which has 256 minor devices associated with it; however, for things like mice this is extremely wasteful, since these drivers only require a single minor number. Use of the misc device allows different drivers to share a single major device number.

Minor numbers in this space are allocated by a central source, although you can look in the file Documentation/devices.txt in the kernel source tree and pick a free one for development use. This kernel file also carries instructions for registering a device. This may change over time so it is a good idea to obtain a current copy of this file first.

Our code then is fairly simple. We check that nobody else has taken our address space. Having done so we reserve it to ensure nobody stomps on our device while probing for other ISA bus devices; such a probe might confuse our mouse.

Then we tell the misc driver that we wish to own a minor number. We also hand it our name (which is used in /proc/misc) and a set of file operations that are to be used. The file operations work exactly like the file operations you would register for a normal character device. The misc device itself is simply acting as a redirector for requests.

Next, in order to be able to use and test our driver we need to add some module wrapper code to support it. As you can see from Listing 2, this is fairly simple.




Listing 2: Module Wrapper Code


#ifdef MODULE

int init_module(void)
{
if(ourmouse_init()<0)
return -ENODEV:
return 0;
}

void cleanup_module(void)
{
misc_deregister(&our_mouse);
free_region(OURMOUSE_BASE, 3);
}

#endif

The module code provides the two functions init_module and cleanup_module. init_module is called when the module is loaded. In our case it simply calls the initializing function we wrote and returns an error if this fails. This ensures the module will only be loaded if it was successfully set up.

The cleanup_module function is called when the module is unloaded. We give the miscellaneous device entry back, and then free our I/O resources. If we didn’t free the I/O resources, then the next time the module loaded it would think someone else had its I/O space.

Once misc_deregister has been called, any attempts to open the mouse device will fail with the error ENODEV (“No such device”).

File Operations

Next we need to fill in our file operations (Listing 3). A mouse doesn’t need many of these, just open, release, read and poll. That makes for a simple structure.




Listing 3: File Operations


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 – 2.2 only */
close_mouse, /* Called on close */
NULL, /* No media change on a mouse */
NULL, /* Asynchronous I/O – we will add
this later*/
};

Nothing special is needed here. We provide functions for the required operations and little else. There is nothing stopping us providing an ioctl function for this mouse. Indeed,if you have a configurable mouse it may be very appropriate to provide configuration interfaces via ioctl calls.

The open and close routines need to manage enabling and disabling interrupts for the mouse as well as basic driver housekeeping (Listing 4).




Listing 4: Managing Interrupts


static int mouse_users = 0; /* User count */
static int mouse_dx = 0; /* Position changes */
static int mouse_dy = 0;
static int mouse_event = 0; * Mouse has moved */

static int open_mouse(struct inode *inode, struct
file *file)
{
if(mouse_users++)
return 0;
if(request_irq(mouse_intr, OURMOUSE_IRQ, 0,
“ourmouse”, NULL))
{
mouse_users–;
return -EBUSY;
}
mouse_dx = 0;
mouse_dy = 0;
mouse_buttons = 0;
mouse_event = 0;
MOD_INC_USE_COUNT;
}

The open function has to do a small amount of work. It maintains a count of the number of times the mouse is open. This is because we do not want to request the interrupt multiple times. If the mouse has at least one user, then it is already set up and we simply add to the user count and return success.

We grab the interrupt so we can start receiving mouse interrupts. If the interrupt line is in use by some other driver then request_irq will fail and we will return an error. If our driver were capable of sharing an interrupt line, we would specify SA_SHIRQ instead of zero as the third argument to request_irq. If all drivers claiming an interrupt set this flag, they get to share the line. Devices on the PCI bus can share interrupts; normally, ISA devices cannot.

open_mouse sets the current mouse position as the starting point for accumulated changes,clears the buttons flag, and sets the flag indicating a new mouse event to zero.

Finally, we call MOD_INC_USE_COUNT to ensure that while the mouse is open nobody will unload it and cause a nasty crash.

The close_mouse function needs to unwind these operations (Listing 5).




Listing 5: The close_mouse Function


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

We keep track of the mouse users. If there are still other users, we need take no further action. If the user count drops to zero, we free the interrupt. This prevents mouse interrupts from causing interrupt handlers to be called (which wastes CPU time if there are no users listening to the mouse). The use of MOD_DEC_USE_COUNT allows the driver to be unloaded from the kernel.

We can fill in the write handler at this point, which simply declines to allow writes (Listing 6).




Listing 6: Filling in the Write Handler


static ssize_t write_mouse(struct file
*file, const char *buffer, size_t
count, loff_t *ppos)
{
return -EINVAL;
}

This is pretty much self-explanatory.

The Interrupt Handler

To make the poll and read functions work we have to consider how to handle the mouse interrupt.

The interrupt handler (Listing 7) reads the mouse status and checks whether anything has changed since the last interrupt. (If the mouse is smart enough, it will only interrupt us when something has changed, however let’s assume that our mouse is as stupid as most mice tend to be.) If so then we update the driver’s internal copy of the mouse state variables. To prevent other functions in the driver from reading the mouse status while we are in the interrupt, we add a spinlock that protects these variables while we update them.




Listing 7: The Interrupt Handler


static struct wait_queue *mouse_wait;
static spinlock_t mouse_lock = SPIN_LOCK_UNLOCKED;

static void ourmouse_interrupt(int irq, void *dev_id,
struct pt_regs *regs)
{
char delta_x;
char delta_y;
unsigned char new_buttons;

delta_x = inb(OURMOUSE_BASE);
delta_y = inb(OURMOUSE_BASE+1);
new_buttons = inb(OURMOUSE_BASE+2);

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;
mouse_buttons = new_buttons;
spin_unlock(&mouse_lock);

wake_up_interruptible(&mouse_wait);
}
}

If a change has occurred we also need to wake any processes sleeping on a new mouse event, so we add a wakeup call and a wait_queue for processes to sleep on.

With the interrupt routine in place, the poll function is relatively easy.

Listing 8 contains fairly standard polling code. First, we add the wait queue to the list of queues we want to monitor for an event. Next, we check if an event has occurred. Here, there is only one kind of event: the mouse_event flag indicates whether there was a change in the mouse status. If the flag is set, we return status indicating that input and normal reading will succeed. If the flag is not set we return 0, meaning “no event yet.” In that case, the poll routine will be called again later and we can check once more for a mouse event.




Listing 8: The poll function


static unsigned int mouse_poll(struct
file *file, poll_table *wait)

{
poll_wait(file, &mouse_wait, wait);
if(mouse_event)
return POLLIN | POLLRDNORM;
return 0;
}

Handling Events

After the poll completes, the user will want to read the data. We now need to think about how our read function will work.

Listing 9 starts by validating that the user is reading enough data. We could handle partial reads if we wanted to, but it isn’t terribly useful and most mouse drivers don’t bother.




Listing 9: Waiting for an Event


static ssize_t mouse_read(struct file *file, char *buffer,
size_t count, loff_t *pos)
{
int dx, dy;
unsigned char button;
unsigned long flags;
int n;

if(count<3)
return -EINVAL;

/*
* Wait for an event
*/
while(!mouse_event)
{
if(file->f_flags&O_NDELAY)
return -EAGAIN;
interruptible_sleep_on(&mouse_wait);
if(signal_pending(current))
return -ERESTARTSYS;
}

Next we wait for an event to occur. The loop is fairly standard for event waiting in Linux. If no event has occurred, we sleep on the mouse_wait queue until one does, or until a signal occurs. Allowing signals is important, as it allows the user to kill processes waiting for the mouse (say, by hitting the interrupt key) — clearly a desirable property. If we are interrupted, we exit the call and the kernel will then process signals and maybe restart the system call.

A user process can set the O_NDELAY flag
on a file to indicate that it wishes to be told immediately if no event is pending. Returning -EAGAIN is the appropriate way to return from a read function with no data to be read.

Note that this code contains a common driver bug (a race condition when testing the mouse_event flag). I will describe how to fix the problem in next month’s column; for now let’s move on.

The next step is to capture the data from the mouse event (Listing 10). To be sure that the event data is not being updated as we capture it, we acquire a spinlock (remember that the interrupt routine acquired the same lock while updating the mouse event data). Here, we use spin_lock_irqsave, which disables interrupts on the local processor. This prevents a deadlock where the interrupt handler spins trying to acquire the spinlock while the read routine already holds it.




Listing 10: Reading the Event


/* mouse_read continued */

/* Grab the event */

spin_lock_irqsave(&mouse_lock, flags);

dx = mouse_dx;
dy = mouse_dy;
button = mouse_buttons;

if(dx<=-127)
dx=-127;
if(dx>=127)
dx=127;
if(dy<=-127)
dy=-127;
if(dy>=127)
dy=127;

mouse_dx -= dx;
mouse_dy -= dy;

if (mouse_dx == 0 && mouse_dy == 0)
mouse_event = 0;

spin_unlock_irqrestore(&mouse_lock, flags);

There is a little cleverness in the event reporting mechanism. The dx and dy values are limited to the range of +/-127; instead of throwing out bad values outside of this range, we limit the reported movement to the extremities of the allowed range, and deduct the reported amount from the “true” mouse movement (in mouse_dx and mouse_dy). The idea is that mouse movements outside of the allowed range should generate additional mouse events until the entire movement is accounted for (in chunks of +/-127 apiece). When the mouse_dx and mouse_dy counts hit zero, we clear the mouse_event flag as there is nothing left to report.

Finally we must copy the results to the user’s buffer (Listing 11). We cannot do this while holding the lock as a write to user memory may sleep. Each put_user call is filling in one byte of the buffer. If it returns an error we inform the program that it passed us an invalid buffer and abort.




Listing 11: Copying Results to the Buffer


/* mouse_read continued */

if(put_user(button|0×80, buffer))
return -EFAULT;
if(put_user((char)dx, buffer+1))
return -EFAULT;
if(put_user((char)dy, buffer+2))
return -EFAULT;

for(n=3; n < count; n++)
if(put_user(0×00, buffer+n))
return -EFAULT;

return count;
}

To Be Continued…

Having written the data, we clear the rest of the user’s buffer and report the read as being successful.

We now have an almost perfectly usable mouse driver. If you were to actually try and use it, however, you would eventually find a couple of problems. And it won’t work with a few programs, as we haven’t added asynchronous I/O support to the driver. But I have run out of space this month, so I’ll leave you with this basic driver and explain the bugs and asynchronous I/O next time. Until then, you might want to have a look at other device drivers to get a feel for how this all works.





Alan Cox is a well-known Linux hacker. He is working on the 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