Blogia
Las Pequeñas Paranoyas de Motagirl

Acceso a la tabla de vectores de interrupción

Otro bonito capítulo sobre mis prácticas de periféricos, de esos que nadie comenta pero luego cuando nos cruzamos por el campus me rascais la cabeza y me dais las gracias :P

Esta no pensaba hacerla todavía,  pero por lo visto esta mañana  a las 7.00 mis vecinos han considerado divertido poner música a todo volumen (nunca antes odié a Bach) y pasear con tacones (o equivalente). Así que como no podía dormir, y no tenía nada mejor que hacer (en realidad sí, pero no importa), le he dedicado un rato a esta práctica que es muy sencilla.

La tabla de vectores de interrupción es un cacho de memoria (concretamente, desde el 0000:0000) en  el que se almacenan las direcciones de las funciones o rutinas que atenderán a cada interrupción. Es decir, que cuando se produce una  interrupción, se transfiere el control del sistema a la rutina situada en la dirección indicada en la posición de memoria especificada en la posición correspondiente de este vector.

Hay 256 interrupciones (la lista está aquí).Cada una, usa 4bytes de este vector: 2bytes para el offset y otros 2bytes para el segmento en que se encuentra la función (no, no me he equivocado: guarda primero el offset y después el segmento). Por tanto, en total la tabla ocupa 1KB. Además, cada interrupción está en la posición indicada por su número: La dirección de la interrupción 0 se encuentra en los 4 primeros bytes, la de la interrupción 1 en los 4 siguientes, etc. En definitiva: el offset de la dirección de la interrupción i se encuentra en el byte  tvi+4*i y su segmento en el tvi+4*i+2 (suponiendo tvi el inicio de la tabla de vectores de interrupción, o sea, 0000:0000)

Supongamos que no nos gusta la rutina normal de teclado, y queremos una propia que por ejemplo, en vez de escribir el carácter por pantalla, imprima el valor del código de teclado (es decir, el código de la tecla pulsada, que no depende del carácter sino  de la posición de la tecla y de si se pulsa o se suelta. Por ejemplo,  el ESC genera el scancode 1, el 1 genera el código 2, el 2 el 3, etc etc. La tabla completa está aquí -la Tabla90-). Podría ser algo tal que así:

void interrupt rutina_teclado(){

char codigo;
codigo = inport(0x60);

/*Blablablabla hacer cosas blablabla*/
outport (0x20, 0x20);

}

Inciso: Sí, pongo las llaves así, mucha gente lo ve raro pero ... a mi me parece más amigable que ambas en nueva línea. :P
Encuesta: ¿Cómo  las pones tú? xD

La palabra interrupt le indica al compilador que lo que está haciendo no es una función normal, sino una interrupción. O sea, que le está pidiendo al compilador que genere código para salvar y restaurar el estado de la CPU cada vez que se ejecute ese código (esa es la gracia de las interrupciones)

Con inport(0x60) lo que hace es leer el scancode de la tecla pulsada/soltada, y el outport(0x20, 0x20) simplemente es el  EOI (End Of Interruption), que hace que se salga correctamente de una interrupción. ¡Debemos usarlo siempre para terminar una interrupción!

Bueno, obviamente, esto así solito, en el limbo de los  códigos, no tiene mucho sentido, así que tendríamos que modificar la TVI para que cada vez que pulsemos una tecla, vaya a nuestra maravillosa rutina en vez de a la normalita. Como ya sabemos, la interrupción de teclado es la 9, así que lo que nos interesa está en esa posición (O sea, necesitamos un punterete hacia MK_FP (0 , 9*4)).

Lo primero, antes de romper nada, es guardarnos la dirección  de la rutina original. (Aunque si se rompe algo, que no panda el cúnico: basta con cerrar y abrir  nuestro maravilloso emulador de DOS). Para ello, guardamos en un par de variables el offset y el segmento, que como dije antes, están respectivamente en los dos primeros y dos siguientes bytes de 0 , 9*4.

Lo siguiente es modificar la posición de la tabla que nos interesa y decirle que ahora tiene que apuntar a nuestra rutina. El identificador de una función es en realidad un puntero a la misma, así que se convierte en algo tan simple como lo que sigue:

asm cli;
*(tvi+9*4)=FP_OFF(rutina_teclado);
*(tvi+9*4+2)=FP_SEG(rutina_teclado);
asm sti;  

Es importante desactivar la llegada de interrupciones antes de cambiar nada. ¿Os imaginais la super catástrofe que sería que se produjera una interrupción de teclado después de haber cambiado el offset pero antes de haber modificado el segmento? ¡Eso sí que sería una fieshta! Podemos hacerlo con  instrucciones en ensamblador: asm cli (desactivarlas) y asm sti (activarlas), una vez que se han modificado. Cuenta la leyenda que también se puede usar disable() y enable(), que están incluidas en dos.h . . . pero yo no las he usado así que no prometo nada.

Y una vez cambiado esto... ya podríamos casi decir que está todo hecho. Eso sí: En el tremendamente abierto "hacer cosas" de la rutina es bastante interesante configurar un caracter "de escape", para que cuando se detecte una  determinada tecla pulsada, se salga de la interrupción restaurando la rutina original (y así además te ahorras tener que cerrar el DOS para poder usar el teclado normalmente xD). Pero eso lo haceis vosotros: no os lo voy a dar todo mascadito :P

Fail típico 1: poner un while(salir==false) dentro de la rutina. Esto es absurdo, porque la rutina se invocará  SIEMPRE que pulse o suelte una tecla, así que el bucle no me hace falta.

Fail típico 2: llamar explícitamente a la función. ¡No hace falta! Vendrá ella solita cada vez que pulses una tecla.

Ale, espero que os haya servido de algo, y si no es así... al menos a mi me ha servido para "afianzar conocimientos". Acepto comentarios, mails y tabletas de chocolate :P

Os dejo con la típica captura de rigor: En ella pulso las teclas M O T A en ese orden. Si pinchais en la imagen y la veis en flickr,  le he puesto notitas explicativas ^^

sesion6 de perifericos

Por cierto, fe de erratas del artículo sobre Modificación de los patrones de bits de los caracteres: Comprobé una vez más el ejecutable sobre cmd, y resulta que no funcionaba por la  simple razón de que no lo tenía configurado en el modo 80x25 y en pantalla completa. En cuanto lo cambié, comenzó a tirar. Cosas que pasan XD

22 comentarios

Nike Shox Shoes -

Your weblog is so cool that I like it particularly a lot. As everyone understands respect is essentially the most important among people's life. Only respect every other to have along effectively and I think that leaving one's opinion is known as a behavior of respect.

motagirl2 -

Jaja gracias :)

GatoVolador -

Debo agradecerte y te agradezco tu iluminación en la oscura ciencia de los Periféricos :P

Orlando -

Disculpen por la pregunta pero, como que daria una funcion hecha en C. Para que al ejecutar el programa se abra en pantalla completa

utilizando windows xp y copilandolo en Turbo C

miguel -

Jope! altra volta!! jeje, bueno que hay que hacer un blucle recorriendo los 4k de la tabla y asignando los contenidos de los punteros uno a uno, y que a ver si salen las prácticas gordas que serán las más importantes de cara al examen y, por cierto, a alguien le va bien la 5? comentad en el foro: http://penyainformatica.mforos.com/821675-perifericos/

Salut!

miguel -

Ey, no sé por qué no se ha copiado mi mensaje entero, el bucle es for(j=0;j

miguel -

Por fiiiiin!!! he visto la luz!! jejeje, bueno más bien me la ha mostrado el profesor por email diciéndome que soy un melón por lo de *p=*q, eso sólo copiaría el primer byte, hay que poner for(j=0;j

miguel -

Vale, voy pillando... jeje, le mandé una tutoría al profesor y me ha dicho lo mismo, entonces creo que lo que hacía mal es que declaraba un puntero que apuntaba a la memoria de vídeo e intentaba modificarla desde ahí, en lugar de copiarla. Pero lo he modificado y ahora me saca basura por pantalla... sería tal que así, no?:

[por cierto, ya había visto los otros dos posts tuyos... :P]


char *p,*q,c;
struct REGPACK rp;
rp.r_ax=0x1130;
rp.r_bx=0x0600;
intr(0x10,&rp);

q=(char *)MK_FP(rp.r_es,rp.r_bp);
p=malloc(sizeof(char)*4096);
*p=*q;
*(p+65*16)=255;

motagirl2 -

Uhm...en la 4 no hace falta bucle, al ejecutar ya se cambian los patrones y durará hasta que hagas un CLS.

Sobre la practica 4 escribí también un par de posts: http://motagirl.blogia.com/2009/022701-acceso-a-rom-para-obtener-los-patrones-de-bits-de-los-caracteres.php y http://motagirl.blogia.com/2009/030402-modificacion-de-los-patrones-de-bits-de-los-caracteres.php

(Ya me avisas cuando cuelgues lo tuyo jaja)

miguel -

Pero eso que me dices es de la sesión 6, no?, en realidad yo estaba hablando de la segunda parte de la 4, jeje, lo de ponerle una rayita a la A. Sé que este artículo es de acceso a la TVI pero como salió el tema de lo anterior... pues pregunté.

Ah! y no te tienes que disculpar, jejeje, que encima de todo lo que haces... yo intentaré colgar tb todo lo que tenga... cuando lo tenga... jeje

See u

motagirl2 -

Disculpa la tardanza, es que justo me he puesto a mirarlo y me han enviado la orla y me he emocionado xD

Bueno, yo tengo el bucle justo despues del

asm cli;
*(tvi+9*4)=FP_OFF(rutina_teclado);
*(tvi+9*4+2)=FP_SEG(rutina_teclado);
asm sti;

o sea, antes de volver a restaurarlo todo. Quizá falles en otra cosa :/

miguel -

Creo que mi fallo está en el bucle que hago para que no salga el programa... ¿dónde hay que poner ese bulce exactamente? Yo lo ponía después de asm int 10h; que es cuando se supone que la interrupción se ejecuta, no? y antes de todo lo demás que hace que vuelva estar todo como antes, no? Tb lo he probado poner al final pero nada... ¿Alguien sabe algo?

Gracias!!

Carlos -

Muy buenos tus post sobre perifericos.

Con tu explicación creo que me has ahorrado 5 horas, por lo que...te debo 5 horas ;)

mgisbert -

Ey, ya me he hecho amigo del DosBox, jeje, pero sigue sin pirulaaar!! jejeje, el rollo es asín, no?:

int main(){
char *q;
struct REGPACK rp;
rp.r_ax=0x1130;
rp.r_bx=0x0600;
intr(0x10,&rp);

q=(char *)MK_FP(rp.r_es,rp.r_bp);

*(q+65*16)=255;

asm push ax;
asm push bx;
asm push cx;
asm push dx;
asm push es;
asm push bp;
asm mov ax, 1110h;
asm mov bx, 1000h;
asm mov cx, 0100h;
asm mov dx, 0;
asm les bp, q;
asm int 10h;
asm pop bp;
asm pop es;
asm pop dx;
asm pop cx;
asm pop bx;
asm pop ax;
}

motagirl2 -

mmmm ni idea, pero a mí el profesor me recomendó hacerlo en dosbox porque es una emulación completa de Dos, no como cmd,... en el que puede no funcionar algo.

mgisbert -

Ey, me parece que la forma elegante de poner las llaves es como las pone motagirl... pero a lo que iba... jeje, con respecto a la fe de erratas del artículo sobre Modificación de los patrones de bits de los caracteres, lo tengo en 80x25 y lo pongo en pantalla completa pero sigue sin pirular en cmd... ¿alguien sabe algo más?...
Esque el dosbox y yo no nos llevamos muy bien...

See u

Chuky -

Mhuhahaha yo también uso las llaves asi, para abrir y cerrar puertas O.o

Respecto a lo del bucle infinito while(true) xDD

motagirl2 -

Yo también soy del while(1). De hecho lo del for(;;) ni se me había ocurrido xD

haruma -

Sobre las llaves. Tú las pones al estilo de las "Java Code Conventions". Lo común en C es poner la llave de abrir en una nueva línea.

De todas formas esto es tema para un debate largo e irresoluble, del estilo de cómo hacer un bucle infinito. Todavía recuerdo un libro que decía que el método estándar para hacer un bucle infinito en C era for(;;), aunque "había una fiel minoría de programadores" que usaban while(1) (soy de esa minoría).

Contra-encuesta: ¿cómo hacéis un bucle infinito?

Scipion -

muy bien mota, las llaves las pones así pq las llaves se ponen así.
krone, siempre hay un raro/siervo de satan/amigo de microsoft que las pone mal pero que le vamos a hacer, el mundo es injusto.
P.D.: hagamos un grupo en facebook de llaves bien puestas y otro de fans de los lenguajes que llevan llaves (no me mola nada el if_then_fi etc.) XD

motagirl2 -

bieeen!!
Hacemos un grupo de facebook? XD

krone -

Yo también pongo las llaves igual ^_^

Los que lo ponen al revés son unos raros :P