Chapter 3. Depurando el Controlador del Rat�n

Ahora tenemos un controlador de rat�n usable bastante perfecto. Si realmente fueras a probarlo y usarlo en todos los casos eventualmente encontrar�as un par de problemas con el. Unos pocos programas no trabajar�n con ya que todav�a no soporta E/S as�ncrona.

Primero d�janos mirar los fallos. El m�s obvio no es realmente un fallo del controlador sino un fallo al considerar las consecuencias. Imag�nate que accidentalmente golpees fuerte el rat�n y lo env�es desliz�ndose sobre la mesa. La rutina de interrupci�n del rat�n a�adir� todo el movimiento y lo reportar� en pasos de 127 hasta que lo haya reportado todo. Claramente hay un punto lejano desde el cual el valor del movimiento del rat�n no es reportado. Necesitamos a�adir esto como un l�mite al manejador de interrupciones:


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)
        {
                /* Algo ha pasado */

                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);
        }
}
  

A�adiendo estos chequeos limitamos el rango de movimiento acumulado a algo sensible.

El segundo fallo es un poco m�s disimulado, y quiz�s porque es un fallo com�n. Recuerda, dije que esperando en el bucle por el manejador de lecturas ten�a un fallo. Piensa en qu� pasa cuando ejecutamos:


        while(!mouse_event)
        {
  

y una interrupci�n ocurre aqu�, en este punto. Esto causa un movimento del rat�n y despierta la cola.


                interruptible_sleep_on(&mouse_wait);
  

Ahora dormimos en la cola. Perdimos el despertar y la aplicaci�n no ver� el evento hasta que ocurra el siguiente evento del rat�n. Esto llevar� justamente a la instancia suelta cuando un bot�n del rat�n se retrasa. Las consecuencias para el usuario ser�n bastante indetectables con un controlador de rat�n. Con otros controladores este fallo podr�a ser mucho m�s severo.

Hay dos formas de solucionar esto. La primera es deshabilitar las interrupciones mientras el testeo y mientras que dormimos. Esto funciona porque cuando una tarea duerme cesa de deshabilitar las interrupciones, y cuando se reinicia las deshabilita otra vez. Nuestro c�digo entonces se convierte en:


        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);
  

Esta es la aproximaci�n bruta. Funciona pero significa que gastamos un mont�n de tiempo adicional cambiando las interrupciones de habilitadas a deshabilitadas. Tambi�n afecta a las interrupciones globalmente y tiene malas propiedades en m�quinas multiprocesadores donde el apagar las interrupciones no es una operaci�n simple, sino que significa hacerlo en cada procesador, esperando por ellos para que deshabiliten las interrupciones y repliquen.

El problema real es la carrera entre la prueba de eventos y el dormir. Podemos eliminar esto usando directamente las funciones de planificaci�n. Realmente esta es la forma que generalmente deber�amos de usar para una interrupci�n.


        struct wait_queue wait = { current, NULL };

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

A primera vista esto probablemente parezca magia profunda. Para entender c�mo trabaja esto necesitas entender c�mo trabajan la planificaci�n y los eventos en Linux. Teniendo un buen dominio de esto es una de las claves para escribir controladores de dispositivos eficientes y claros.

add_wait_queue hace lo que su nombre sugiere. A�ade una entrada a la lista mouse_wait. La entrada en este caso es la entrada para nuestro proceso actual (current es el puntero de la tarea actual).

Por lo tanto, empezamos a�adiendo una entrada para nosotros mismos en la lista mouse_wait. Esto de cualquier forma no nos pone a dormir. Meramente estamos unidos a la lista.

A continuaci�n establecemos nuestro status a TASK_INTERRUPTIBLE. Otra vez esto no significa que no estamos dormidos. Este flag dice lo que deber�a de pasar la siguiente vez que el proceso duerma. TASK_INTERRUPTIBLE dice que el proceso no deber�a de ser replanificado. �l se ejecutar� desde ahora hasta que duerma y entonces necesitar� ser despertado.

La llamada wakeup_interruptible en el manejador de interrupciones puede ahora ser explicada con m�s detalle. Esta funci�n es tambi�n muy simple. Va a trav�s de la lista de procesos en la tarea que le es dada y cualquiera que est� marcada como TASK_INTERRUPTIBLE la cambia a TASK_RUNNING y dice al n�cleo que son ejecutables nuevos procesos.

Detr�s de todos los envoltorios en el c�digo original lo que est� sucediendo es esto:

  1. Nos a�adimos nosotros mismos a la cola de espera del rat�n

  2. Nos marcamos como durmiendo

  3. Preguntamos al n�cleo para planificar tareas otra vez

  4. El n�cleo ve que estamos durmiento y planifica alg�n otro.

  5. La interrupci�n del rat�n establece nuestro estado a TASK_RUNNING y destaca que el n�cleo deber�a replanificar tareas

  6. El n�cleo ve que estamos ejecut�ndonos otra vez y contin�a nuestra ejecuci�n

Esto es porque funciona la aparentemente magia. Porque nos marcamos como TASK_INTERRUPTIBLE y nos a�adimos a la cola antes de chequear si hay eventos pendientes, la condici�n de carrera es eliminada.

Ahora si ocurre una interrupci�n despu�s de que chequeemos el estado de la cola y antes de llamar a la funci�n schedule en orden a dormir, las cosas resultan. En vez de perder un evento, estamos volviendo a establecer TASK_RUNNING por la interrupci�n del rat�n. Todav�a llamamos a schedule pero el continuar� ejecutando nuestra tarea. Volvemos a trav�s del bucle y esta vez quiz�s exista un evento.

No habr� siempre un evento. Entonces nos volveremos a establecer a TASK_INTERRUPTIBLE antes de continuar el bucle. Otro proceso haciendo una lectura quiz�s haya limpiado el flag de eventos y si es as� necesitaremos regresar a dormir otra vez. Eventualmente obtendremos nuestro evento y salimos.

Finalmente cuando salimos del bucle nos quitamos de la cola mouse_wait, ya que no estamos m�s interesados en eventos del rat�n, y ahora nos volvemos a establecer a TASK_RUNNABLE ya que todav�a no queremos ir a dormir otra vez.

NoteNota
 

Este no es un t�pico f�cil. No tengas miedo de releer la descripci�n unas pocas veces y tambi�n de mirar en otros controladores de dispositivos para ver si funciona. Finalmente si todav�a no puedes cogerlo, puedes usar el c�digo como modelo para escribir otros controladores de dispositivos y confiar en m�.