Sophie

Sophie

distrib > Mandriva > 9.0 > i586 > by-pkgid > 98e91bc877e03cf3582cd163550eb7e3 > files > 713

kernel-doc-html-2.4.19-16mdk.i586.rpm

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<HTML
><HEAD
><TITLE
>A simple mouse driver</TITLE
><META
NAME="GENERATOR"
CONTENT="Modular DocBook HTML Stylesheet Version 1.7"><LINK
REL="HOME"
TITLE="Mouse Drivers"
HREF="book1.html"><LINK
REL="PREVIOUS"
TITLE="Introduction"
HREF="c20.html"><LINK
REL="NEXT"
TITLE="Debugging the mouse driver"
HREF="c131.html"></HEAD
><BODY
CLASS="CHAPTER"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="NAVHEADER"
><TABLE
SUMMARY="Header navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TH
COLSPAN="3"
ALIGN="center"
>Mouse Drivers</TH
></TR
><TR
><TD
WIDTH="10%"
ALIGN="left"
VALIGN="bottom"
><A
HREF="c20.html"
ACCESSKEY="P"
>&#60;&#60;&#60; Previous</A
></TD
><TD
WIDTH="80%"
ALIGN="center"
VALIGN="bottom"
></TD
><TD
WIDTH="10%"
ALIGN="right"
VALIGN="bottom"
><A
HREF="c131.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="CHAPTER"
><H1
><A
NAME="DRIVER"
></A
>A simple mouse driver</H1
><P
>    First we will need the set up functions for our mouse device. To keep 
    this simple our imaginary mouse device has three I/O ports fixed at I/O 
    address 0x300 and always lives on interrupt 5.  The ports will be the X 
    position, the Y position and the buttons in that order.
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>#define OURMOUSE_BASE        0x300

static struct miscdevice our_mouse = {
        OURMOUSE_MINOR, "ourmouse", &#38;our_mouse_fops
};

__init ourmouse_init(void)
{

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

        misc_register(&#38;our_mouse);
        return 0;
}
  </PRE
></TD
></TR
></TABLE
><P
>    The <SPAN
CLASS="STRUCTNAME"
>miscdevice</SPAN
> is new here. Linux normally 
    parcels devices out by major number, and each device has 256 units. 
    For things like mice this is extremely wasteful so a device exists 
    which is used to accumulate all the odd individual devices that 
    computers tend to have.
  </P
><P
>    Minor numbers in this space are allocated by a central source, although 
    you can look in the kernel <TT
CLASS="FILENAME"
>Documentation/devices.txt</TT
>
    file 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.
  </P
><P
>    Our code then is fairly simple. We check nobody else has taken our 
    address space. Having done so we reserve it to ensure nobody stamps 
    on our device while probing for other ISA bus devices. Such a probe 
    might confuse our device.
  </P
><P
>    Then we tell the misc driver that we wish to own a minor number. We also
    hand it our name (which is used in 
    <TT
CLASS="FILENAME"
>/proc/misc</TT
>) 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.
  </P
><P
>    Next, in order to be able to use and test our code we need to add some 
    module code to support it. This too is fairly simple:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>#ifdef MODULE

int init_module(void)
{
        if(ourmouse_init()&#60;0)
                return -ENODEV:
        return 0;
}

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


#endif
  </PRE
></TD
></TR
></TABLE
><P
>    The module code provides the normal two functions. The 
    <TT
CLASS="FUNCTION"
>init_module</TT
> function is called when the module is 
    loaded. In our case it simply calls the initialising function we wrote 
    and returns an error if this fails. This ensures the module will only 
    be loaded if it was successfully set up.
  </P
><P
>    The <TT
CLASS="FUNCTION"
>cleanup_module</TT
> 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.
  </P
><P
>    Once the <TT
CLASS="FUNCTION"
>misc_deregister</TT
> has been called any 
    attempts to open the mouse device will fail with the error  
    <SPAN
CLASS="ERRORCODE"
>ENODEV</SPAN
> (<SPAN
CLASS="ERRORNAME"
>No such device</SPAN
>).
  </P
><P
>    Next we need to fill in our file operations. A mouse doesn't need many 
    of these. We need to provide open, release, read and poll. That makes 
    for a nice simple structure:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>struct file_operations our_mouse_fops = {
        owner: THIS_MODULE,            /* Automatic usage management */
        read:  read_mouse,             /* You can read a mouse */
        write: write_mouse,            /* This won't do a lot */
        poll:  poll_mouse,             /* Poll */
        open:  open_mouse,             /* Called on open */
        release: close_mouse,          /* Called on close */
};
  </PRE
></TD
></TR
></TABLE
><P
>    There is nothing particularly special needed here. We provide functions 
    for all the relevant or 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.
  </P
><P
>    The syntax we use is not standard C as such. GCC provides the ability
    to initialise fields by name, and this generally makes the method table
    much easier to read than counting through NULL pointers and remembering
    the order by hand.
  </P
><P
>    The owner field is used to manage the locking of module load an
    unloading. It is obviously important that a module is not unloaded while
    in use. When your device is opened the module specified by "owner" is 
    locked. When it is finally released the module is unlocked.
  </P
><P
>    The open and close routines need to manage enabling and disabling the 
    interrupts for the mouse as well as stopping the mouse being unloaded
    when it is no longer required. 
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>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_event = 0;
        mouse_buttons = 0;
	return 0;
}
  </PRE
></TD
></TR
></TABLE
><P
>    The open function has to do a small amount of housework. We keep 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 set up and we simply add to the user count and return
    <SPAN
CLASS="RETURNVALUE"
>0</SPAN
> for success.
  </P
><P
>    We grab the interrupt and thus start mouse interrupts. If the interrupt 
    has been borrowed by some other driver then <TT
CLASS="FUNCTION"
>request_irq</TT
>
    will fail and we will return an error. If we were capable of sharing an 
    interrupt line we would specify <TT
CLASS="CONSTANT"
>SA_SHIRQ</TT
> instead of 
    <TT
CLASS="CONSTANT"
>zero</TT
>. Provided that everyone claiming an interrupt 
    sets this flag, they get to share the line. <TT
CLASS="HARDWARE"
><B
>PCI</B
></TT
> can 
    share interrupts, <TT
CLASS="HARDWARE"
><B
>ISA</B
></TT
> normally however cannot. 
  </P
><P
>    We do the housekeeping. We make the current mouse position the starting
    point for accumulated changes and declare that nothing has happened
    since the mouse driver was opened.
  </P
><P
>    The release function needs to unwind all these:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static int close_mouse(struct inode *inode, struct file *file)
{
        if(--mouse_users)
                return 0;
        free_irq(OURMOUSE_IRQ, NULL);
        return 0;
}
  </PRE
></TD
></TR
></TABLE
><P
>    We count off a user and provided that there are still other users need 
    take no further action. The last person closing the mouse causes us to 
    free up the interrupt. This stops interrupts from the mouse from using 
    our CPU time, and ensures that the mouse can now be unloaded.
  </P
><P
>    We can fill in the write handler at this point as the write function for 
    our mouse simply declines to allow writes:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static ssize_t write_mouse(struct file *file, const char *buffer, size_t
                                count, loff_t *ppos)
{
        return -EINVAL;
}
  </PRE
></TD
></TR
></TABLE
><P
>    This is pretty much self-explanatory. Whenever you write you get told 
    it was an invalid function.
  </P
><P
>    To make the poll and read functions work we have to consider how we 
    handle the mouse interrupt. 
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>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(&#38;mouse_lock);
                mouse_event = 1;
                mouse_dx += delta_x;
                mouse_dy += delta_y;
                mouse_buttons = new_buttons;
                spin_unlock(&#38;mouse_lock);
                
                wake_up_interruptible(&#38;mouse_wait);
        }
}
  </PRE
></TD
></TR
></TABLE
><P
>    The interrupt handler reads the mouse status. The next thing we do is 
    to check whether something has changed. If the mouse was smart it would
    only interrupt us if something had changed, but let's assume our mouse 
    is stupid as most mice actually tend to be. 
  </P
><P
>    If the mouse has changed we need to update the status variables. What we
    don't want is the mouse functions reading these variables to read them
    during a change. We add a spinlock that protects these variables while we
    play with them.
  </P
><P
>    If a change has occurred we also need to wake sleeping processes, so we 
    add a wakeup call and a <SPAN
CLASS="STRUCTNAME"
>wait_queue</SPAN
> to use when 
    we wish to await a mouse event.
  </P
><P
>    Now we have the wait queue we can implement the poll function for the 
    mouse relatively easily:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static unsigned int mouse_poll(struct file *file, poll_table *wait)
{
        poll_wait(file, &#38;mouse_wait, wait);
        if(mouse_event)
                return POLLIN | POLLRDNORM;
        return 0;
}
  </PRE
></TD
></TR
></TABLE
><P
>    This is fairly standard poll code. First we add the wait queue to the 
    list of queues we want to monitor for an event. Secondly we check if an 
    event has occurred. We only have one kind of event - the 
    <TT
CLASS="VARNAME"
>mouse_event</TT
> flag tells us that something happened. 
    We know that this something can only be mouse data. We return the flags 
    indicating input and normal reading will succeed.
  </P
><P
>    You may be wondering what happens if the function returns saying 'no 
    event yet'. In this case the wake up from the wait queue we added to 
    the poll table will cause the function to be called again. Eventually 
    we will be woken up and have an event ready. At this point the 
    <TT
CLASS="FUNCTION"
>poll</TT
> call will exit back to the user.
  </P
><P
>    After the poll completes the user will want to read the data. We now 
    need to think about how our <TT
CLASS="FUNCTION"
>mouse_read</TT
> function 
    will work:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>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&#60;3)
                return -EINVAL;

        /*
          *        Wait for an event
         */

        while(!mouse_event)
        {
                if(file-&#62;f_flags&#38;O_NDELAY)
                        return -EAGAIN;
                interruptible_sleep_on(&#38;mouse_wait);
                if(signal_pending(current))
                        return -ERESTARTSYS;
        }
  </PRE
></TD
></TR
></TABLE
><P
>    We start by validating that the user is reading enough data. We could 
    handle partial reads if we wanted but it isn't terribly useful and the 
    mouse drivers don't bother to try.
  </P
><P
>    Next we wait for an event to occur. The loop is fairly standard event
    waiting in Linux. Having checked that the event has not yet occurred, we
    then check if an event is pending and if not we need to sleep. 
  </P
><P
>    A user process can set the <TT
CLASS="CONSTANT"
>O_NDELAY</TT
> flag on a file 
    to indicate that it wishes to be told immediately if no event is 
    pending. We check this and give the appropriate error if so. 
  </P
><P
>    Next we sleep until the mouse or a signal awakens us. A signal will 
    awaken us as we have used <TT
CLASS="FUNCTION"
>wakeup_interruptible</TT
>. 
    This is important as it means a user can kill processes waiting for 
    the mouse - clearly a desirable property. If we are interrupted we 
    exit the call and the kernel will then process signals and maybe 
    restart the call again - from the beginning.
  </P
><P
>    This code contains a classic Linux bug. All will be revealed later in this
    article as well as explanations for how to avoid it.
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>        /* Grab the event */

        spinlock_irqsave(&#38;mouse_lock, flags);

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

        if(dx&#60;=-127)
                dx=-127;
        if(dx&#62;=127)
                dx=127;
        if(dy&#60;=-127)
                dy=-127;
        if(dy&#62;=127)
                dy=127;

        mouse_dx -= dx;
        mouse_dy -= dy;
        
        if(mouse_dx == 0 &#38;&#38; mouse_dy == 0)
                mouse_event = 0;

        spin_unlock_irqrestore(&#38;mouse_lock, flags);
  </PRE
></TD
></TR
></TABLE
><P
>    This is the next stage. Having established that there is an event 
    going, we capture it. To be sure that the event is not being updated 
    as we capture it we also take the spinlock and thus prevent parallel 
    updates. Note here we use <TT
CLASS="FUNCTION"
>spinlock_irqsave</TT
>. We 
    need to disable interrupts on the local processor otherwise bad things 
    will happen.
  </P
><P
>    What will occur is that we take the spinlock. While we hold the lock 
    an interrupt will occur. At this point our interrupt handler will try 
    and take the spinlock. It will sit in a loop waiting for the read 
    routine to release the lock. However because we are sitting in a loop 
    in the interrupt handler we will never release the lock. The machine 
    hangs and the user gets upset.
  </P
><P
>    By blocking the interrupt on this processor we ensure that the lock 
    holder will always give the lock back without deadlocking.
  </P
><P
>    There is a little cleverness in the reporting mechanism too. We can 
    only report a move of 127 per read. We don't however want to lose 
    information by throwing away further movement. Instead we keep 
    returning as much information as possible. Each time we return a 
    report we remove the amount from the pending movement in 
    <TT
CLASS="VARNAME"
>mouse_dx</TT
> and <TT
CLASS="VARNAME"
>mouse_dy</TT
>. Eventually 
    when these counts hit zero we clear the <TT
CLASS="VARNAME"
>mouse_event</TT
>
    flag as there is nothing else left to report.
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>        if(put_user(button|0x80, 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 &#60; count; n++)
                if(put_user(0x00, buffer+n))
                        return -EFAULT;

        return count;
}
  </PRE
></TD
></TR
></TABLE
><P
>    Finally we must put the results in the user supplied buffer. We cannot 
    do this while holding the lock as a write to user memory may sleep. 
    For example the user memory may be residing on disk at this instant. 
    Thus we did our computation beforehand and now copy the data. Each 
    <TT
CLASS="FUNCTION"
>put_user call</TT
> 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.
  </P
><P
>    Having written the data we blank the rest of the buffer that was read 
    and report the read as being successful.
  </P
></DIV
><DIV
CLASS="NAVFOOTER"
><HR
ALIGN="LEFT"
WIDTH="100%"><TABLE
SUMMARY="Footer navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
><A
HREF="c20.html"
ACCESSKEY="P"
>&#60;&#60;&#60; Previous</A
></TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="book1.html"
ACCESSKEY="H"
>Home</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
><A
HREF="c131.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>Introduction</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
>&nbsp;</TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>Debugging the mouse driver</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>