Sophie

Sophie

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

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

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<HTML
><HEAD
><TITLE
>Debugging the 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="A simple mouse driver"
HREF="c53.html"><LINK
REL="NEXT"
TITLE="Asynchronous I/O"
HREF="c190.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="c53.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="c190.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="CHAPTER"
><H1
><A
NAME="DEBUGGING"
></A
>Debugging the mouse driver</H1
><P
>    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 with it. A few programs will also not work with as it does not 
    yet support asynchronous I/O.
  </P
><P
>    First let us look at the bugs. The most obvious one isn't really a driver
    bug but a failure to consider the consequences. Imagine you bumped the 
    mouse hard by accident and sent it skittering across the desk. The mouse 
    interrupt routine will add up all that movement and report it in steps of 
    127 until it has reported all of it. Clearly there is a point beyond 
    which mouse movement isn't worth reporting. We need to add this as a 
    limit to the interrupt handler:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>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;

                if(mouse_dx &#60; -4096)
                        mouse_dx = -4096;
                if(mouse_dx &#62; 4096)
                        mouse_dx = 4096;

                if(mouse_dy &#60; -4096)
                        mouse_dy = -4096;
                if(mouse_dy &#62; 4096)
                        mouse_dy = 4096;

                mouse_buttons = new_buttons;
                spin_unlock(&#38;mouse_lock);
                
                wake_up_interruptible(&#38;mouse_wait);
        }
}
  </PRE
></TD
></TR
></TABLE
><P
>    By adding these checks we limit the range of accumulated movement to
    something sensible. 
  </P
><P
>    The second bug is a bit more subtle, and that is perhaps why this is 
    such a common mistake. Remember, I said the waiting loop for the read 
    handler had a bug in it. Think about what happens when we execute:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>        while(!mouse_event)
        {
  </PRE
></TD
></TR
></TABLE
><P
>    and an interrupt occurs at this point here. This causes a mouse movement
    and wakes up the queue. 
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>                interruptible_sleep_on(&#38;mouse_wait);
  </PRE
></TD
></TR
></TABLE
><P
>    Now we sleep on the queue. We missed the wake up and the application 
    will not see an event until the next mouse event occurs. This will 
    lead to just the odd instance when a mouse button gets delayed. The 
    consequences to the user will probably be almost undetectable with a 
    mouse driver. With other drivers this bug could be a lot more severe.
  </P
><P
>    There are two ways to solve this. The first is to disable interrupts 
    during the testing and the sleep. This works because when a task sleeps 
    it ceases to disable interrupts, and when it resumes it disables them 
    again. Our code thus becomes:
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>        save_flags(flags);
        cli();

        while(!mouse_event)
        {
                if(file-&#62;f_flags&#38;O_NDELAY)
                {
                        restore_flags(flags);
                        return -EAGAIN;
                }
                interruptible_sleep_on(&#38;mouse_wait);
                if(signal_pending(current))
                {
                        restore_flags(flags);
                        return -ERESTARTSYS;
                }
        }
        restore_flags(flags);
  </PRE
></TD
></TR
></TABLE
><P
>    This is the sledgehammer approach. It works but it means we spend a 
    lot more time turning interrupts on and off. It also affects 
    interrupts globally and has bad properties on multiprocessor machines 
    where turning interrupts off globally is not a simple operation, but 
    instead involves kicking each processor, waiting for them to disable 
    interrupts and reply.
  </P
><P
>    The real problem is the race between the event testing and the sleeping. 
    We can avoid that by using the scheduling functions more directly. 
    Indeed this is the way they generally should be used for an interrupt.
  </P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>        struct wait_queue wait = { current, NULL };

        add_wait_queue(&#38;mouse_wait, &#38;wait);
        set_current_state(TASK_INTERRUPTIBLE);
        
        while(!mouse_event)
        {
                if(file-&#62;f_flags&#38;O_NDELAY)
                {
                        remove_wait_queue(&#38;mouse_wait, &#38;wait);
                        set_current_state(TASK_RUNNING);
                        return -EWOULDBLOCK;
                }
                if(signal_pending(current))
                {
                        remove_wait_queue(&#38;mouse_wait, &#38;wait);
                        current-&#62;state = TASK_RUNNING;
                        return -ERESTARTSYS;
                }
                schedule();
                set_current_state(TASK_INTERRUPTIBLE);
        }
        
        remove_wait_wait(&#38;mouse_wait, &#38;wait);
        set_current_state(TASK_RUNNING);
  </PRE
></TD
></TR
></TABLE
><P
>    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.
  </P
><P
>    <TT
CLASS="FUNCTION"
>add_wait_queue</TT
> does what its name suggests. It adds 
    an entry to the <TT
CLASS="VARNAME"
>mouse_wait</TT
> list. The entry in this 
    case is the entry for our current process (<TT
CLASS="VARNAME"
>current</TT
>
    is the current task pointer). 
  </P
><P
>    So we start by adding an entry for ourself onto the 
    <TT
CLASS="VARNAME"
>mouse_wait</TT
> list. This does not put us to sleep 
    however. We are merely tagged onto the list. 
  </P
><P
>    Next we set our status to <TT
CLASS="CONSTANT"
>TASK_INTERRUPTIBLE</TT
>. Again 
    this does not mean we are now asleep. This flag says what should happen 
    next time the process sleeps. <TT
CLASS="CONSTANT"
>TASK_INTERRUPTIBLE</TT
> says 
    that the process should not be rescheduled. It will run from now until it 
    sleeps and then will need to be woken up.
  </P
><P
>    The <TT
CLASS="FUNCTION"
>wakeup_interruptible</TT
> call in the interrupt 
    handler can now be explained in more detail. This function is also very 
    simple. It goes along the list of processes on the queue it is given and 
    any that are marked as <TT
CLASS="CONSTANT"
>TASK_INTERRUPTIBLE</TT
> it changes 
    to <TT
CLASS="CONSTANT"
>TASK_RUNNING</TT
> and tells the kernel that new 
    processes are runnable.
  </P
><P
>    Behind all the wrappers in the original code what is happening is this
  </P
><DIV
CLASS="PROCEDURE"
><OL
TYPE="1"
><LI
><P
>      We add ourself to the mouse wait queue
    </P
></LI
><LI
><P
>      We mark ourself as sleeping
    </P
></LI
><LI
><P
>      We ask the kernel to schedule tasks again
    </P
></LI
><LI
><P
>      The kernel sees we are asleep and schedules someone else.
    </P
></LI
><LI
><P
>      The mouse interrupt sets our state to <TT
CLASS="CONSTANT"
>TASK_RUNNING</TT
> 
      and makes a note that the kernel should reschedule tasks
    </P
></LI
><LI
><P
>      The kernel sees we are running again and continues our execution
    </P
></LI
></OL
></DIV
><P
>    This is why the apparent magic works. Because we mark ourself as
    <TT
CLASS="CONSTANT"
>TASK_INTERRUPTIBLE</TT
> and as we add ourselves 
    to the queue before we check if there are events pending, the race 
    condition is removed.
  </P
><P
>    Now if an interrupt occurs after we check the queue status and before 
    we call the <TT
CLASS="FUNCTION"
>schedule</TT
> function in order to sleep, 
    things work out. Instead of missing an event, we are set back to 
    <TT
CLASS="CONSTANT"
>TASK_RUNNING</TT
> by the mouse interrupt. We still call 
    <TT
CLASS="FUNCTION"
>schedule</TT
> but it will continue running our task. 
    We go back around the loop and this time there may be an event.
  </P
><P
>    There will not always be an event. Thus we set ourselves back to
    <TT
CLASS="CONSTANT"
>TASK_INTERRUPTIBLE</TT
> before resuming the loop. 
    Another process doing a read may already have cleared the event flag, 
    and if so we will need to go back to sleep again. Eventually we will 
    get our event and escape.
  </P
><P
>    Finally when we exit the loop we remove ourselves from the 
    <TT
CLASS="VARNAME"
>mouse_wait</TT
> queue as we are no longer interested
    in mouse events, and we set ourself back to 
    <TT
CLASS="CONSTANT"
>TASK_RUNNABLE</TT
> as we do not wish to go to sleep 
    again just yet.
  </P
><DIV
CLASS="NOTE"
><P
></P
><TABLE
CLASS="NOTE"
WIDTH="100%"
BORDER="0"
><TR
><TD
WIDTH="25"
ALIGN="CENTER"
VALIGN="TOP"
><IMG
SRC="./stylesheet-images/note.gif"
HSPACE="5"
ALT="Note"></TD
><TH
ALIGN="LEFT"
VALIGN="CENTER"
><B
>Note</B
></TH
></TR
><TR
><TD
>&nbsp;</TD
><TD
ALIGN="LEFT"
VALIGN="TOP"
><P
>     This isn't an easy topic. Don't be afraid to reread the description a 
     few times and also 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.
   </P
></TD
></TR
></TABLE
></DIV
></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="c53.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="c190.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>A simple mouse driver</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
>&nbsp;</TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>Asynchronous I/O</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>