Using catalogs: /etc/sgml/catalog Using stylesheet: /usr/share/sgml/docbook/utils-0.6.9/docbook-utils.dsl#html Working on: /home/jdavila/lucas/doc-manual-linux-controladores-raton/doc-manual-linux-controladores-raton.xml Controladores de Rat�n

Controladores de Rat�n

Alan Cox


Table of Contents
1. Introducci�n
2. Un controlador simple de rat�n
3. Depurando el Controlador del Rat�n
4. E/S As�ncrona
5. Sobre la traducci�n
List of Tables
1-1. Codificaci�n de Datos del Rat�n

Chapter 1. Introducci�n

NotePublicaci�n Anterior
 

Algunas partes de este documento aparecieron primero en Linux Magazine bajo una exclusividad de noventa dias.

Los ratones son conceptualmente uno de los interfaces de dispositivos m�s simples en el sistema operativo Linux. No todos los ratones son manejados por el n�cleo. Es vez de eso, hay una abstracci�n de dos capas.

Los controladores de rat�n del n�cleo y los controladores del espacio de usuario para los ratones serie son todos administrados por un demonio del sistema llamado gpm - el controlador de prop�sito general de rat�n. gpm maneja la acci�n de cortar y pegar en los textos de las consolas. Suministra una biblioteca general para aplicaciones que conocen el rat�n y administra la compartici�n de los servicios del rat�n con la interfaz de usuario del X Window System.

Algunas veces un rat�n habla un protocolo suficientemente complicado como para que sea manejado por el propio Gpm. La mayor�a de los controladores de rat�n siguen una interfaz com�n llamada protocolo de bus del rat�n.

Cada lectura de un dispositivo de una interfaz del bus de rat�n retorna un bloque de datos. Los tres primeros bytes de cada lectura est�n definidos de la siguiente forma:

Una aplicaci�n puede escoger leer m�s de 3 bytes. El resto de los bytes ser�n cero, o quiz�s opcionalmente retornen alguna informaci�n espec�fica del dispositivo.

Los valores de la posici�n son truncados si es que exceden del rango de los 8 bits (que es -127 <= delta <= 127). Como el valor -128 no encaja en un byte no es permitido.

Los botones son numerados de izquierda a derecha como 0, 1, 2, 3.. y cada bot�n establece el bit relevante. Por lo tanto un usuario presionando los botonoes de la izquierda y de la derecha en un rat�n de tres botones establecer�n los bits 0 y 2.

Todos los ratones est�n requeridos a soportar la operaci�n poll. Ser�a algo verdaderamente muy bonito si todos los usuarios de un controlador de un dispositivo usaran poll para esperar a que tuvieran lugar los eventos.

Finalmente el soporte as�ncrono de E/S de los ratonoes. Este es un t�pico que todav�a no hemos cubierto pero que explicar� m�s tarde, despu�s de mirar en un controlador simple de rat�n.


Chapter 2. Un controlador simple de rat�n

Primero necesitaremos inicializar las funciones para nuestro dispositivo rat�n. Para mantener esto simple, nuestro dispositivo imaginario de rat�n tiene tres puertos de E/S en las direcciones de E/S 0x300 y siempre vivir� en la interrupci�n 5. Los puertos ser�n la posici�n X, la posici�n Y y los botones, en este orden.


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

El miscdevice es nuevo aqu�. Linux normalmente divide los dispositivos por su n�mero mayor, y cada dispositivo tiene 256 unidades. Para cosas como los ratones esto es extremadamente derrochador para la existencia de un dispositivo que es usado para acumular todos los dispositivos individuales sueltos que las computadoras tienden a tener.

Los n�meros menores en este espacio son asignados por un c�digo central, aunque puedes mirar en el el archivo Documentation/devices.txt del n�cleo y coger uno libre para un uso de desarrollo. Este archivo de n�cleo tambi�n contiene instrucciones para registrar un dispositivo. Esto puede cambiar con respecto al tiempo y es, por lo tanto, una buena idea obtener primero una copia actualizada de este archivo.

Nuestro c�digo es entonces bastante simple. Chequeamos si nadie m�s ha tomado nuestro espacio de direcciones. Habi�ndolo hecho, lo reservamos para asegurarnos de que nadie pisa a nuestro dispositivo mientras estamos probando otros dispositivos del bus ISA. Ya que una prueba quiz�s confunda a nuestro dispositivo.

Entonces le decimos al controlador misc que queremos nuestro propio n�mero menor. Tambi�n cogemos nuestro nombre (que es usado en /proc/misc) y establecemos las operaciones de archivo que van a ser usadas. Las operaciones de archivo trabajan exactamente como las operaciones de archivo para registrar un dispositivo de car�cter normal. El dispositivo misc simplemente act�a como redirector para las peticiones.

Lo siguiente, en orden a ser capaz de usar y probar nuestro propio c�digo, es que necesitamos a�adir alg�n c�digo de m�dulo para soportarlo. Esto tambi�n es bastante simple:


#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
  

El c�digo del m�dulo suministra las dos funciones normales. La funci�n init_module es llamada cuando el m�dulo es cargado. En nuestro caso simplemente llama a la funci�n de inicializaci�n que escribimos y retorna un error si esta falla. Esto asegura que el m�dulo s�lo ser� cargado si fue correctamente configurado.

La funci�n cleanup_module es llamada cuando el m�dulo es descargado. Devolvemos nuestra entrada de dispositivo miscel�neo, y entonces liberamos nuestros recursos de E/S. Si no liberamos nuestros recursos de E/S entonces la siguiente vez que el m�dulo es cargado pensaremos que alguien tiene este espacio de E/S.

Una vez que misc_deregister ha sido llamada cualquier intento de abrir el dispositivo del rat�n fallar� con el error ENODEV (No tal dispositivo).

Lo siguiente que necesitamos es rellenar nuestras operaciones de archivo. Un rat�n no necesita muchas de estas. Necesitamos suministrar open (abrir), release (liberar), read (leer) y poll (encuesta). Esto hace una bonita y simple estructura:


struct file_operations our_mouse_fops = {
        owner: THIS_MODULE,            /* Autom�tica administraci�n de uso */
        read:  read_mouse,             /* Puedes leer un rat�n */
        write: write_mouse,            /* Esto deber�a de hacer un mont�n */
        poll:  poll_mouse,             /* Encuesta */
        open:  open_mouse,             /* Llamado en open */
        release: close_mouse,          /* Llamado en close */
};
  

No hay nada particularmente especial necesitado aqu�. Suministramos funciones para todas las operaciones relevantes o requiridas y algunas pocas m�s. No hay nada que nos pare para suministrar una funci�n ioctl para este rat�n. Verdaderamente si tienes un rat�n configurable quiz�s sea muy apropiado suministrar interfaces de configuraci�n a trav�s de llamadas ioctl.

La sintaxis que usamos no es la del C est�ndar. GCC suministra la habilidad de inicializar campos por el nombre, y esto generalmente hace la tabla de m�todos mucho m�s f�cil de leer y contar a trav�s de los punteros NULL y de recordar el orden a mano.

El due�o del campo es usado para administrar el bloqueo de la carga y descarga de un m�dulo. Esto es obviamente importante para que un m�dulo no sea descargado mientras est� siendo usado. Cuando tu dispositivo es abierto, el m�dulo especificado por "owner" es bloqueado. Cuando el m�dulo es finalmente liberado es desbloqueado.

Las rutinas open y close necesitan administrar el habilitamiento y deshabilitamiento de las interrupciones para el rat�n y tambi�n el parar el rat�n siendo descargado cuando no se requiere m�s.


static int mouse_users = 0;                /* Cuenta de Usuarios */
static int mouse_dx = 0;                   /* Cambios de Posici�n */
static int mouse_dy = 0;
static int mouse_event = 0;                /* El rat�n se movi� */

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

La funci�n open tiene que hacer una peque�a cantidad de tareas dom�sticas. Mantenemos una cuenta del n�mero de veces que el rat�n est� abierto. Esto es porque no queremos pedir la interrupci�n m�ltiples veces. Si el rat�n tiene por lo menos un usuario, es configurado y simplemente a�adimos el usuario a la cuenta y retornamos 0 para indicar que tuvo �xito.

Cogemos la interrupci�n y entonces comienzan las interrupciones del rat�n. Si la interrupci�n ha sido apropiada por otro controlador entonces request_irq fallar� y retornar� un error. Si fuimos capaces de compartir una l�nea de interrupci�n deber�amos de especificar SA_SHIRQ en vez de zero. Siempre que todo el mundo que coga una interrupci�n establezca este flag, compartir�n la l�nea. PCI puede compartir interrupciones, ISA normalmente no.

Hacemos las tareas dom�sticas. Hacemos a la actual posici�n del rat�n el punto de comienzo para los cambios acumulados y declaramos que no ha pasado nada desde que el controlador del rat�n ha sido abierto.

La funci�n release (liberar) necesita desenrollar todas estas:


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

Descontamos un usuario y siempre que todav�a halla otros usuarios que no necesiten acciones adicionales. La �ltima persona cerrando el rat�n causa que liberemos la interrupci�n. Esto para las interrupciones desde el rat�n usando nuestro tiempo de CPU, y asegura que el rat�n puede ser ahora descargado.

Podemos rellenar el manejador de escritura en este punto como la funci�n write para la que nuestro rat�n simplemente declina permitir escrituras:


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

Esto es bastante auto-explicativo. Siempre que escribes dir�n que era una funci�n inv�lida.

Para hacer que las funciones read y poll trabajen tenemos que considerar como manejar las interrupciones de rat�n.


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

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

El manejador de interrupciones lee el status del rat�n. La siguiente cosa que hacemos es chequear cuando algo ha cambiado. Si el rat�n estaba listo s�lo nos deber�a de interrumpir si algo a cambiado, pero asumamos que nuestro rat�n es est�pido, tal como tienden a ser la mayoria de los ratones.

Si el rat�n ha cambiado necesitamos actualizar las variables de estado. Lo que no queremos es que las funciones del rat�n leyendo estas variables lean durante un cambio. A�adimos un spinlock que protega estas variables mientras jugamos con ellas.

Si ha ocurrido un cambio tambi�n necesitamos despertar a los procesos que est�n durmiendo, por lo tanto a�adimos una llamada wakeup (despertar) y una wait_queue para usar cuando queremos esperar un evento de rat�n.

Ahora que tenemos la cola de espera podemos implementar la funci�n poll para el rat�n de una forma relativamente f�cil:


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

Esto es un c�digo de encuesta bastante est�ndar. Primero a�adimos la cola de espera a la lista de colas que queremos monitorizar para un evento. Lo segundo es chequear si ha ocurrido un evento. Nosotros s�lo tenemos un tipo de evento - el flag mouse_event nos dice que algo ha pasado. Conocemos que esto s�lo pueden ser datos del rat�n. Retornamos las flags indicando entrada y realizaremos una lectura normal.

Quiz�s te asombres de lo que pasa si la funci�n retorna diciendo 'todav�a no ocurri� un evento'. En esto caso el despertar de la cola de espera que a�adimos a la tabla poll caurar� que la funci�n sea llamada otra vez. Eventualmente despertaremos y tendremos un evento listo. En este punto la llamada poll puede regresar al usuario.

Despu�s de que poll finalice, el usuario querr� leer los datos. Ahora necesitamos pensar c�mo trabajar� nuestra funci�n mouse_read:


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;

        /*
          *        Espera por un evento
         */

        while(!mouse_event)
        {
                if(file->f_flags&O_NDELAY)
                        return -EAGAIN;
                interruptible_sleep_on(&mouse_wait);
                if(signal_pending(current))
                        return -ERESTARTSYS;
        }
  

Empezamos validando que el usuario est� leyendo suficientes datos. Podr�amos manejar lecturas parciales si quisi�ramos, pero esto no es terriblemente �til y los controladores de los ratones no se preocupan de intentarlo.

Acto seguido esperamos que ocurra un evento. El bucle es bastante est�ndar en Linux para la espera de un evento. Habiendo chequeado que el evento todav�a no ha ocurrido, entonces chequeamos si un evento est� pendiente y si no es as� necesitamos dormir.

Un proceso de usuario puede establecer la flag O_NDELAY en un archivo para indicar que desea comunicar inmediatamente si no hay alg�n evento pendiente. Chequeamos esto y le damos el error apropiado si es as�.

A continuaci�n dormimos hasta que el rat�n o una se�al nos despierte. Una se�al nos despertar� si hemos usado wakeup_interruptible. Esto es importante, ya que significa que un usuario puede matar procesos que est�n esperando por el rat�n - propiedad limpia y deseable. Si somos interrumpidos salimos de la llamada y el n�cleo, entonces, procesar� las se�ales y quiz�s reinicialice la llamada otra vez - desde el principio.

Este c�digo contiene un fallo cl�sico de Linux. Todo ser� revelado despu�s en este articulo, al igual que las explicaciones de c�mo eliminarlas.


        /* Coge el evento */

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

Esta es la siguiente etapa. Habiendo establecido que hay un evento viniendo, lo capturamos. Para asegurarnos de que el evento no est� siedo actualizado cuando lo capturamos tambi�n tomamos el spinlock y esto previene las actualizaciones paralelas. Destacar que aqu� usamos spinlock_irqsave. Necesitamos deshabilitar las interrupciones en el procesador local o en otro caso suceder�n cosas malas.

Lo que ocurrir� es que cogeremos el spinlock. Mientras tenemos el bloqueo ocurrir� una interrupci�n. En este pundo nuestro manejador de interrupciones intentar� coger el spinlock. El se sentar� en un bucle esperando por la rutina de lectura para que libere el bloqueo. De cualquier forma como estamos sentados en un bucle en el manejador de interrupciones nunca liberaremos el bloqueo. La m�quina se cuelga y el usuario se trastorna.

Bloqueando la interrupci�n en este procesador nos aseguramos de que el mantener el bloqueo siempre nos devolver� el bloqueo sin hacer un deadlocking.

Tambi�n hay un peque�o truco en el mecanismo de reporte. S�lo podemos reportar un movimiento de 127 por lectura. En todo caso no queremos perder informaci�n lanzando movimientos adicionales. En vez de esto, nos mantenemos retornando tanta informaci�n como sea posible. Cada vez que retornamos un reporte quitamos la cantidad de movimiento pendiente en mouse_dx y mouse_dy. Eventualmente cuando estas cuentas llegan a cero, limpiamos el flag mouse_event porque ya no queda nada que reportar.


        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 < count; n++)
                if(put_user(0x00, buffer+n))
                        return -EFAULT;

        return count;
}

Finalmente tenemos que poner los resultados en el buffer suministrado por el usuario. No podemos hacer esto mientras mantenemos el bloqueo, ya que una escritura a la memoria de usuario quiz�s duerma. Por ejemplo, la memoria de usuario quiz�s est� residiendo en disco en este instante. Entonces hicimos nuestra computaci�n de antemano y ahora copiamos los datos. Cada put_user call est� rellenando en una byte del buffer. Si retorna un error nosotros informamos al programa que nos est� pasando un buffer inv�lido y abortamos.

Habiendo escrito los datos vaciamos el resto del buffer que leimos y reportamos que la lectura tuvo �xito.


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:

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�.


Chapter 4. E/S As�ncrona

Esto deja la caracter�stica perdida - E/S As�ncrona. Normalmente los programas UNIX usan la llamada poll (o su forma variante select) para esperar a que ocurra un evento en uno de los m�ltiples dispositivos de entrada o salida. Este modelo trabaja bien para la mayor�a de las tareas porque las esperas poll y select para un evento no son convenientes para tareas que est�n continuamente haciendo trabajo computacional. Tales programas realmente quieren que el n�cleo les golpee cuando pasa algo en vez de mirar por los eventos.

Poll es semejante a tener una fila de luces delante de t�. Puedes ver en un instante cuales de ellas est�n encendidas. No puedes, de cualquier forma, hacer nada �til mientras las est�s mirando. La E/S as�ncrona usa se�ales que trabajan m�s bien como un timbre. Es vez de mirar, dice que algo se ha manifestado.

La E/S as�ncrona env�a la se�al SIGIO al proceso de usuario cuando ocurre el evento de E/S. En este caso esto significa cuando la gente mueve el rat�n. La se�al SIGIO causa que el proceso de usuario salga a su manejador de se�ales y ejecute el c�digo en ese manejador antes de regresar a lo que estuviera haciendo previamente. Esta es la aplicaci�n equivalente a un manejador de interrupciones.

La mayor parte del c�digo necesitado para esta operaci�n es com�n a todos los usuarios. El n�cleo suministra un conjunto simple de funciones para administrar la E/S as�ncrona.

Nuestro primer trabajo es permitir a los usuarioes establecer E/S as�ncrona en el manejadores de archivos. Para hacer esto necesitamos a�adir una nueva funci�nn a la tabla de operaciones de archivo para nuestro rat�n:


struct file_operations our_mouse_fops = {
        owner: THIS_MODULE
        read:  read_mouse,      /* Puedes leer un rat�n */
        write: write_mouse,     /* Esto no har� mucho */
        poll:  poll_mouse,      /* Encuesta */
        open:  open_mouse,      /* Llamado en open */
        release: close_mouse,   /* Llamado en close */
        fasync: fasync_mouse,   /* E/S as�ncrona */
};
  

Una vez que hemos instalado esta entrada, el n�cleo conoce que soportamos E/S as�ncrona y permitir� todas las operaciones relevantes en el dispositivo. Siempre que un usuario a�ade o quita la notificaci�n de E/S as�ncrona de un manejador de archivos, llama a nuestra rutina fasync_mouse que acabamos de a�adir. Esta rutina usa las funciones de ayuda para mantener actualizada la cola de manejadores:


static struct fasync_struct *mouse_fasync = NULL;

static int fasync_mouse(int fd, struct file *filp, int on)
{
         int retval = fasync_helper(fd, filp, on, &mouse_fasync);

         if (retval < 0)
                 return retval;
        return 0;
}
  

La fasync helper a�ade y borra entradas administrando la lista suministrada. Tambi�n necesitamos quitar entradas de esta lista cuando es cerradi el archivo. Esto requiere a�adir una l�nea a nuestra funci�n close:


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

Cuando cerramos el archivo podemos llamar a nuestro propio manejador fasync como si el usuario pidiera que este archivo cesara de ser usado para E/S as�ncrona. Esto aproximadamente limpia cualesquiera finales perdidos. Seguramente no esperamos por la llegada de una se�al para un archivo que no existir� m�s.

En este punto, el controlador del rat�n soporta todas las operaciones de E/S as�ncrona, y las aplicaciones us�ndolas no fallar�n. Estas de todas formas no trabajar�n todav�a. Necesitamos realmente enviar las se�ales. Otra vez el n�cleo suministra una funci�n para manejar esto.

Actualizamos un poco nuestro 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);

                /* Ahora hacemos E/S as�ncrona */
                kill_fasync(&mouse_fasync, SIGIO); 
                
                wake_up_interruptible(&mouse_wait);
        }
}
  

El nuevo c�digo simplemente llama a la rutina kill_fasync suminstrada por el n�cleo si la cola no est� vac�a. Esto env�a la se�al requerida (SIGIO en este caso) al proceso que cada manejador de archivo dijo que quer�a ser informado sobre el excitante nuevo movimiento del rat�n que acaba de ocurrir.

Con esto en su sitio y arreglados los fallos en la versi�n original, tienes ahora un controlador de rat�n totalmente funcional usando el protocolo del bus del rat�n. El trabajar� con X window system, trabajar� con GPM y deber�a de trabajar con todas las otras aplicaciones que necesites. Doom es, por supuesto, la forma ideal para probar que tu nuevo controlador de rat�n est� funcionando de forma adecuada. Aseg�rate de probarlo de todas las formas posibles.


Chapter 5. Sobre la traducci�n

Este documento es la traducci�n de "Mouse Drivers", documento que acompa�a al c�digo del n�cleo de Linux, versi�n 2.4.18.

Este documento ha sido traducido por Rub�n Melc�n ; y es publicado por el Proyecto Lucas

Versi�n de la traduci�n 0.04 ( Julio de 2002 ).

Si tienes comentarios sobre la traducci�n, ponte en contacto con Rub�n Melc�n

Done.