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.