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:0x80 + 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 0x85 (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 0x300-0x302 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 0x300
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").