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.