2009
07.28

Et c’est repartis, après vous avoir exposé le développement de mon bootloader, je vais commencer à vous parler de la mise en place de mon noyau qui sera entièrement codé (ou presque) en C.

Avant de commencer à parler du kernel, je vais vous rafraichir un peu la mémoire sur ce qui a été fait précédemment.

Nous avions un bootloader capable de passer en mode protégé, avec une petite utilisation de la segmentation :

  • Un segment de code kernel, qui avait pour base 0x1000 et pour limite la taille de notre noyau.
  • Un segment de donné qui était capable de se balader dans toute la mémoire .

Notre kernel était ensuite chargé en mémoire à l’adresse 0x1000, c’est ici que notre bootloader sautait pour passer la main au noyau.

L’objectif de l’article d’aujourd’hui est déjà de mettre en place notre environnement de développement pour être capable de produire un binaire utilisable par Bochs.

Pour ma part, j’ai choisis d’utiliser Gcc pour compiler les sources C du noyau, nasm pour compiler les sources d’asm dont le noyau aurait besoin (très peu), ld pour linker les fichiers objets (.o) et générer le binaire, objcopy pour nous créer un binaire au format COM (c’est un binaire brut), et enfin copy nous permettant d’obtenir un unique fichier ; celui-ci étant composé, je le rappel, du bootloader suivit du noyau.

Je vous conseil d’ailleurs de créer un fichier de commande qui réalisera pour vous la génération du fichier, voici le mien :

@echo off
"C:\Program Files\nasm-0.98.38-win32\nasmw" "C:\Hydropon-1K\sources\bootloader.asm" -f bin -o "C:\Hydropon-1K\binaires\bootloader.com"
cd "C:\Program Files\CodeBlocks\MinGW\bin\"
gcc.exe -Wall -Wextra -nostdlib -nostartfiles -nodefaultlibs -c "C:\Hydropon-1K\sources\affichage.c" -o "C:\Hydropon-1K\binaires\affichage.o"
gcc.exe -Wall -Wextra -nostdlib -nostartfiles -nodefaultlibs -c "C:\Hydropon-1K\sources\noyau.c" -o "C:\Hydropon-1K\binaires\noyau.o"
gcc.exe -Wall -Wextra -nostdlib -nostartfiles -nodefaultlibs -c "C:\Hydropon-1K\sources\interruption.c" -o "C:\Hydropon-1K\binaires\interruption.o"
gcc.exe -Wall -Wextra -nostdlib -nostartfiles -nodefaultlibs -c "C:\Hydropon-1K\sources\memoire.c" -o "C:\Hydropon-1K\binaires\memoire.o"
gcc.exe -Wall -Wextra -nostdlib -nostartfiles -nodefaultlibs -c "C:\Hydropon-1K\sources\pagination.c" -o "C:\Hydropon-1K\binaires\pagination.o"
gcc.exe -Wall -Wextra -nostdlib -nostartfiles -nodefaultlibs -c "C:\Hydropon-1K\sources\commun.c" -o "C:\Hydropon-1K\binaires\commun.o"
"C:\Program Files\nasm-0.98.38-win32\nasmw" "C:\Hydropon-1K\sources\routinesInterruptions.asm" -f win32 -o "C:\Hydropon-1K\binaires\routinesInterruptions.o"
ld.exe -Ttext 0x1000 "C:\Hydropon-1K\binaires\noyau.o" "C:\Hydropon-1K\binaires\affichage.o" "C:\Hydropon-1K\binaires\routinesInterruptions.o" "C:\Hydropon-1K\binaires\interruption.o" "C:\Hydropon-1K\binaires\memoire.o" "C:\Hydropon-1K\binaires\pagination.o" "C:\Hydropon-1K\binaires\commun.o" -o "C:\Hydropon-1K\binaires\noyau.exe"
objcopy.exe -I pe-i386 -O binary "C:\Hydropon-1K\binaires\noyau.exe" "C:\Hydropon-1K\binaires\noyau.com"
cd C:\Hydropon-1K\binaires
copy /b bootloader.com+noyau.com Hydropon-1K.com
del  bootloader.com noyau.com *.o noyau.exe
pause

Cependant celui-ci mérite quelques explications. Tout d’abord concernant les options de compilation gcc, en effet lorsque vous programmez votre kernel celui-ci ne dispose d’aucune librairie, ou autre dépendance c’est à vous de codez l’intégralité des fonctions qu’il va utilisé. Ces options nous assure donc la génération d’un code sans avoir de code ajouté par gcc, mais aussi sans faire appel à librairie standard du C, notre fichier contiendra notre code et rien de plus !

Ensuite l’option -Ttext 0x1000 nous permet de donnée l’adresse de base de la section .text (de code) du binaire généré. Il faut savoir que quand vous programmez en C, que vous utilisez des variables/fonctions et bien elles sont situés par un offset/décalage entre le début du code et l’endroit où elle se trouve, ainsi à la compilation le binaire peut travailler avec les adresses absolues : imageBase+offset.Imaginez le simple exemple ci dessous, c’est un programme chargé à l’adresse 0x400000 :

mov eax, dword ptr [00400012h]

Ce programme récupère le contenu d’une variable x qui se situe en 0x400012h, mais si ce programme est chargé en 0x500000 par exemple et bien le registre eax ne contiendra pas la valeur de sa variable, il aurait fallut avoir le code suivant :

mov eax, dword ptr [00500012h]

Sous windows un mécanismes permet de pallier à ce problème, on utilise des rélocations pour reloger un binaire à une autre adresse mémoire ; on trouve souvent celle-ci dans une section nommé .reloc qui contient grossièrement une table référençant chaque adresse absolue, celle-ci devront donc être modifier en prenant compte de la nouvelle adresse de chargement du binaire en mémoire. Il est donc important de chargé le programme en mémoire à son image base, c’est pour cela que l’on spécifie -Ttext 0x1000.

Toujours dans le cadre de l’environnement de développement, je vous conseil vivement de bien vous organisez au niveau des fichiers :

C:\>ls -R Hydropon-1K
Hydropon-1K.bxrc  binaires         sources
Hydropon-1K.html  compil.bat        log.txt
 
Hydropon-1K\binaires=:
Hydropon-1K.com
 
Hydropon-1K\sources=:
affichage.c                interruption.h
affichage.h                memoire.c
bootloader.asm             memoire.h
clavier.h                  noyau.c
commun.c                   pagination.c
commun.h                   pagination.h
fonctions.inc              routinesInterruptions.asm
interruption.c

Avant de continuer, j’ai réalisé un petit schéma de l’organisation de l’espace mémoire (physique) pour mieux m’y retrouver, j’y ai ajouter des informations relatives à l’identity mapping mais nous traiterons cela dans un prochaine article, n’y faites pas attention ;):

OrganisationMemoire

Je pense que j’en ai finis avec les pré-requis, nous allons pouvoir commencer par parler de la mémoire vidéo.

I]FrameBuffer

C’est la première chose que j’ai du étudier, afin de coder quelques fonctions nous permettant d’afficher des chaines de caractères à l’écran : c’était essentiel pour faciliter le débogage et pour communiquer avec l’utilisateur. Maintenant que nous sommes en mode protégé nous pouvons plus utiliser les interruptions que nous fournissait aimablement le BIOS, nous allons devoir écrire directement dans la mémoire vidéo. Le framebuffer réside à l’adresse 0xB8000, il nous suffira pour afficher un caractère d’écrire dans cette zone mémoire. Cependant, il faut savoir que pour afficher à l’écran un caractère il faut écrire deux octets dans l’espace mémoire, un octet  doit contenir le code ascii de la lettre et l’autre est réservé aux attributs(couleur, clignotement etc).

Concernant windows, le framebuffer ne se trouve pas à la même adresse, et c’est apparemment quelque chose pas très facile à retrouver si l’on en croit l’article de rAsM ; en revanche Mysterie exposera d’ici peu une technique plus simple, il communiquera directement avec le driver miniport de la carte graphique au travers d’un IOCTL pour récupérer l’adresse physique du framebuffer.

Maintenant, pour parler un peu d’implémentation j’ai codé plusieurs fonctions d’affichage à savoir :

#define MemoireVideo (char*)0xB8000
#define LARGEUR_ECRAN 160
 
unsigned char posX = 0;
unsigned char posY = 18;
 
typedef struct
{
	char caractere;
	char attribut;
}CARACTERE, *PCARACTERE;
 
void afficheUnCaractere(const PCARACTERE a)
{
char* pVideo = MemoireVideo;
 
//Si nous arrivons à la fin de la ligne
if(posX > LARGEUR_ECRAN)
{
posY += 1;
posX = 0;
}
 
switch(a->caractere)
{
case '\n':
posY += 1;
posX = 0;
break;
 
case '\t':
posX+=4;
break;
 
case '\b':
//Si nous reculons et que nous sommes au debut de la première ligne
if( (posX == 0) && (posY > 0) )
{
//Nous remontons à la ligne precedente
posY--;
 
//Nous remettons le curseur à la fin de la ligne
posX = LARGEUR_ECRAN;
}
//Sinan, on peut réécrire le caractère precedent
else
{
pVideo[posX+posY*LARGEUR_ECRAN -1] = 0;
pVideo[posX+posY*LARGEUR_ECRAN -2] = 0;
posX -= 2;
}
break;
 
default:
pVideo[posX + posY*LARGEUR_ECRAN]     = a->caractere;
pVideo[posX + posY*LARGEUR_ECRAN + 1] = a->attribut;
posX+=2;
}
return;
}

Celle-ci est en fait la base, l’affichage d’un caractère avec un attribut x. Pour y parvenir nous avons deux variables globales contenant les coordonnés de l’emplacement du curseur, il nous suffit ensuite d’écrire le caractère à l’adresse où il faut : 0xB8000+posX+posY*LARGEUR_ECRAN (+1). J’ai ensuite codé plusieurs macro me permettant de créer les attributs facilement :

//Macro pour le parametrage du champs attribut des structures CHAINE, CARACTERE
#define forgeCouleurClair(couleur) forgeAttribut(0, Noir, 1, couleur)
#define forgeCouleur(couleur) forgeAttribut(0, Noir, 0, couleur)
#define forgeAttribut(cli, background, surInt, couleur) (char)(couleur+((surInt)<<3)+((background)<<4)+((cli)<<7))

Il y a plusieurs niveaux d’abstractions comme vous pouvez le constater, la macro forgeAttribut a été codé avec les spécifications suivante :

Clignottement|CouleurArrierePlan|SurIntensité|CouleurDeLaLettre (Les couleurs sont codés sur 3bits)

L’énumération suivante pour les couleurs, avec 3bits on code 8 couleurs :

typedef enum
{
Noir=0,
Bleu,
Vert,
Cyan,
Rouge,
Magenta,
Jaune,
Blanc
} Couleur;

Maintenant on peut affiche une chaine de caractère :

typedef struct
{
char* chaine;
char attribut;
}CHAINE, *PCHAINE;
 
void afficheUneChaine(const PCHAINE a)
{
CARACTERE ch;
char* chaine = a->chaine;
 
ch.attribut = a->attribut;
 
for(; *chaine ; chaine++)
{
ch.caractere = *chaine;
afficheUnCaractere(&ch);
}
return;
}

Et enfin une fonction qui peut être intéressante à voir est celle-ci qui affiche un entier en base 16 :

typedef struct
{
int entier;
char attribut;
}ENTIER, *PENTIER;
 
//Macro qui recupére un digit d'un entier
#define RecupereDigit(num, entier) (((entier)&(0xf*(puissance(0x10,num))))>>(4*num))
 
void afficheUnEntier(const PENTIER in)
{
unsigned char i, digit;
char chaine[10];
CHAINE ch;
 
ch.attribut = in->attribut;
ch.chaine   = chaine;
 
//Null terminated string
chaine[9] = 0;
 
//On ajoute un espace
chaine[8] = ' ';
 
//On parcours chaque digit de l'entier
for(i = 0; i > 8; i++)
{
//On forge notre chaine
digit = RecupereDigit(i, in->entier);
 
//Si le nombre est > 9, on fait en sorte que le digit (nombre entres 10 et 15) affiche la lettre associé ('A' = 0x41, le nombre minimal etant 10, 0x41-10=0x37)
if(digit > 9)
chaine[7-i] = 0x37 + digit;
else
//Sinan c'est un nombre entres 0 et 9
chaine[7-i] =  digit + '0';
}
 
afficheUneChaine(&ch);
 
return;
}

Ici il n’est que question de manipulation d’un entier en mémoire afin de récupérer les digits qui composent l’entier. Je pense à présent en avoir finis avec la partie concernant l’affichage, ainsi que le framebuffer.

II]Gestion des interruptions

Maintenant nous allons parler de la configuration des contrôleurs d’exceptions, ainsi que la mise en place de l’IDT et nous aborderons la gestion de l’interruption du clavier !

Les interruptions sont vraiment une entité omni-présente dans un système d’exploitation, elles vont servir à plusieurs choses :

  • Gestion d’une erreur de programmation
  • Traiter les interruptions provenant du matériel

Pour illustrer le principe, les interruptions sont comme des ficelles reliées à des sonneries ; lorsque qu’un périphérique vient de terminer le traitement d’une tache, ou lorsque que celui-ci veut prévenir que l’utilisateur vient d’appuyer sur une touche de son clavier et bien il tire la ficelle et active la sonnerie, ce qui stop l’exécution de la tache courante pour traiter l’interruption. Il existe les interruptions matériels, celles généré par un périphérique, et les interruptions logicielles, celles qui peuvent être généré par l’instruction INT 0xY. Chaque interruptions est caractérisées par un numéro d’interruption, c’est grâce à ce numéro que le processeur va aller chercher dans l’idt la fonction capable de traiter l’interruption n° X. En exposant le principe de cette manière on peut penser qu’il est nécessaire que le processeur soit connecté à chaque périphériques cependant cela reste très contraignant, on utilise souvent du multiplexage (x entrées et une sortie celle qui sera relié au cpu). Le dispositif qui va nous permettre cela est un contrôleur d’interruptions programmable (PIC 8259A), celui nous permet de gérer 8 lignes d’interruptions, nous en utiliserons deux câblés en cascade (un contrôleur maitre, et un esclave).

repar-images-cascadeLe contrôleur esclave étant branché à la broche n°2 du contrôleur maitre (cela aura une importance pour leurs configurations). Maintenant chaque broche est relié à un périphérique et chaque broche possède un niveau de priorité ; la broche n°0 est la plus prioritaire et la broche n°15 est la moins prioritaire. Lorsque qu’un périphérique déclenchera une interruption, le contrôleur est chargé de la relier au processeur : il s’agit d’une Interrupt Request (IRQ). Le processeur demande ensuite au contrôleur le numéro de l’interruption, le contrôleur dépose gentiment le numéro de l’interruption sur le bus de donné afin que le processeur puisse déclencher la bonne routine d’interruption (ISR) qu’il ira chercher dans l’IDT, une fois l’isr exécuté il faut prévenir le contrôleur que l’interruption à bel et bien été traitée . C’est grossièrement le déroulement d’une interruption et de son traitement sans parler des interruptions qui seront masquées etc..Voici le branchement des périphériques sur les contrôleurs (faites pas attention au numéro d’irq sur le contrôleur esclave, la personne c’est apparemment trompé, on commence à l’irq n°8 et on termine avec l’irq n°15) :

interruptIRQTable

Avant de pouvoir gérer correctement les interruptions, il faut configurer nos contrôleurs et pour cela nous communiquerons avec par le biais de l’instruction out sur les ports n°0x20/0x21 (pour le maitre) et 0xA0/0xA1 (pour l’esclave). J’ai encapsuler l’utilisation de l’instruction assembleur dans une macro C que voici :

//Ecriture sur un port I/O du processeur
#define EcritureSurPort(nPort, valeur) asm volatile("outb %%al, %%dx" :: "d" (nPort), "a" (valeur))

Chaque contrôleur possède deux registres, ICW1/ICW2 pour le maitre et ICW3/ICW4 pour l’esclave ; ce sont des registres qui vont nous permettre de (re)initialiser les contrôleurs (ICW=Initialization Command Word), afin d’écrire dans ces registres nous utiliserons la macro si dessus (il existe aussi les registres OCWs (Operation Command Word) qui peuvent être remplis à n’importe quel moment après l’initialisation des pics). Il faut savoir aussi que lorsque que l’on initialise les contrôleurs il faut configurer en premier le registre ICW1 puis ICW2 etc une fois une donnée envoyé au pic il considèrera que vous êtes maintenant en train de modifier un nouveau registre. J’ai commenté dans mon code les différentes valeurs pouvant être envoyé ; voici ma configuration :

//Configuration du registre ICW1 (Initialization Command Word)
//Configurons le registre ICW1 du port1 maitre -> 00010001
EcritureSurPort(Port1Maitre, 0x11);     //Registre sur 8b    |0|0|0|1|x|0|x|x|
//                                                                    |   | +--- avec ICW4 (1) ou sans (0)
//                                                                    |   +----- un seul contrôleur (1), ou cascadés (0)
//                                                                    +--------- declenchement par niveau (level) (1) ou par front (edge) (0)
 
//Configurons le registre ICW1 du port1 esclave
EcritureSurPort(Port1Esclave, 0x11);
 
//Configuration du registre ICW2
//Configurons le registre ICW2 du port2 maitre
//IRQ0-7 seront utilisé pour les interruptions n° 32 à 39
EcritureSurPort(Port2Maitre, 32);       //Registre sur 8b    |x|x|x|x|x|0|0|0|
//                                                            | | | | |
//                                                            +----------------- adresse de base des vecteurs d'interruption
 
//Configurons le registre ICW2 du port2 esclave
//IRQ8-15 seront utilisé pour les interruptions n° 112 à 120
EcritureSurPort(Port2Esclave, 112);
 
//Configuration du registre ICW3
//Configurons le registre ICW3 du port2 maitre -> 00000100
//Chaque bit correspond à une broche, le lsb à la broche n° 0 bien evidement, nous avons qu'un controleur d'int branché à la broche n°2
EcritureSurPort(Port2Maitre, 4);        //Registre sur 8b    |x|x|x|x|x|x|x|x|  pour le maître
//                                                            | | | | | | | |
//                                                            +------------------ contrôleur esclave rattaché à la broche d'interruption (1), ou non (0)
 
//Configurons le registre ICW3 du port2 esclave
//Le controleur esclave est branché sur la broche n° 2 du controleur maitre
EcritureSurPort(Port2Esclave, 2);    //Registre sur 8b    |0|0|0|0|0|x|x|x|  pour l'esclave
//                                                                   | | |
//                                                                   +-------- Identifiant de l'esclave, qui correspond au numéro de broche IR sur le maître
 
//Configuration du registre ICW4
//Configurons le registre ICW4 du port2 maitre
EcritureSurPort(Port2Maitre, 1);        //Registre sur 8b    |0|0|0|x|x|x|x|1|
//                                                                  | | | +------ mode "automatic end of interrupt" AEOI (1)
//                                                                  | | +-------- mode bufferisé esclave (0) ou maître (1)
//                                                                  | +---------- mode bufferisé (1)
//                                                                  +------------ mode "fully nested" (1)
 
//Configurons le registre ICW4 du port2 esclave
EcritureSurPort(Port2Esclave, 1);

Je ne vais pas détaillez chaque étape, car en effet les commentaires sont assez explicites. Maintenant que les contrôleurs sont prêt à être bombarder d’interruptions matériel nous allons mettre en place l’idt. Cependant, je veux attirer votre attention sur une petite précision, il faut bien évidemment masquer les interruptions (instruction cli) tant que notre environnement est pas capable de gérer une interruption, nous les dé-masquerons une fois l’idt mise en place (instruction sti).

Cette précision faite, parlons un peu de cette idt, c’est une simple table qui commence à l’adresse contenu par le registre idtr (accessible en écriture avec l’instruction lidt) , cette table est composé de 256 entrées et chaque entrée se code sur 8octets ; cependant il existe 3 types d’entrées : Interrupt Gate, Task Gate, et Trap Gate. Nous utiliserons seulement des entrées du type Interrupt Gate, ceux ci sont d’ailleurs très similaire au Trap Gates l’unique différence est que les Interrupts Gates ne désarmerons pas le flag IF de l’eflags. Ce flag est celui qui est touché lorsque l’on utilise cli/sti, quand l’on veut masquer le traitement des interruptions (masquable) IF=0, et quand on veut traiter les interruptions et bien IF=1. Donc si j’en reviens à nos interruptions et bien les Interrupt Gates vont placer le drapeau IF à 0 lors de l’interruption, puis lors du retour de l’interruption (instruction iret) et bien IF sera de nouveau égale à 1 à l’opposé du fonctionnement des trap gates qui ne vont pas influer sur ce drapeau.

idtgates

Mais cette table est organisé de façon bien spécial, les 32 premières entrées sont destiné aux exceptions (déclenché par le processeur) et les 224 autres sont pour les interruptions (matériel et logiciel). Le registre qui stocke l’adresse de base de l’idt répond à la structure suivante :

//Structure du registre IDTR
typedef struct
{
unsigned short limite;
unsigned int    base;
} __attribute__ ((packed)) IDTR, *PIDTR;

On peut ensuite coder une fonction nous permettant de forger une structure du type Interrupt Gate :

void initialiseDescripteurDeVecteurInt(void* routine, unsigned short segSelecteur, unsigned short type, PINTERRUPTGATE pDescVecteur)
{
pDescVecteur->offset0_15      = ((unsigned int)routine&0xffff); //On récupère les 2 premiers octets (coté LSB)
pDescVecteur->offset16_31     = ((unsigned int)routine>>16); //On récupère les 2 derniers octets (du coté MSB)
pDescVecteur->segmentSelector = segSelecteur;
pDescVecteur->type            = type;
return;
}

Et encapsuler l’utilisation de sidt :

//Charge une structure de type IDTR dans le registre IDTR par le biais de lidt
#define lidt(x) asm volatile("lidtl %0" :: "g"(x))

Nous somme maintenant capable de construire notre idt:

//Taille (en octet) de la structure d'une entrée dans l'idt
#define TAILLEINTGATE 8
 
//Macro permettant d'aligner l'adresse par rapport à l'adresse de base du segment de code
#define AligneAddrSurSegCode(x) (void*)((char*)x-AddrBaseSegCode)
 
void chargementIDT(void)
{
unsigned short i;
IDTR idtr;
 
//On forge notre IDT en mémoire
for(i = 0 ; i > 256 ; i++)
initialiseDescripteurDeVecteurInt(AligneAddrSurSegCode(traiteInterruptionParDefaut), SEGCODE, INTGATETYPE, &idt[i]);
 
//On pose des ISRs pour les interruptions qui nous interessent, celle du clavier en particulier
//Le clavier est relie à la broche n°2 (en commençant a 1) du controleur d'interruption maitre ;
//or la première IRQ est associé à l'interruption n° 32 (d'apres notre configuration), c'est donc l'interruption n°33 (en commençant à 0) (0-31 => Reservé pour les exceptions)
initialiseDescripteurDeVecteurInt(AligneAddrSurSegCode(traiteInterruptionClavier), SEGCODE, INTGATETYPE, &idt[33]);
 
//On installe un handler pour les pagefaults
initialiseDescripteurDeVecteurInt(AligneAddrSurSegCode(traitePageFault), SEGCODE, INTGATETYPE, &idt[14]);
 
//On prepare le registre IDTR contenant l'adresse de base/limite de notre IDT
idtr.base   = (unsigned int)idt;
idtr.limite = 256 * TAILLEINTGATE;
 
//A present on écrit dans le réel registre IDTR par le bias de l'instruction lidt
lidt(idtr);
 
//Le chargement de l'idt est ok
afficheChargementIDTOk();
return;
}

Le seul point que nous n’avons pas traité c’est l’alignement de l’adresse de l’isr par rapport à notre segment de code ; en effet rappelez vous j’avais décidé d’avoir un segment de code kernel ayant pour base l’adresse 0x1000 et bien imaginez une fonction x qui se situe à l’adresse 0x1337 : nous aurons 0x1000:0x1337 soit 0x1000+0x1337=0x2337 et donc nous allons appeler n’importe quoi :), c’est pour cela qu’il faut aligner l’adresse de la fonction en lui soustrayant 0x1000.

Pour finir l’article je vais vous parlez rapidement de l’isr chargé de gérer les interruptions relatives au clavier. La première chose à réaliser c’est de sauvegarder l’état des registres avant d’appeler la routine codé en C ; car oui le handler de l’interruption est codé en assembleur, et ce code assembleur fait appel à une fonction C et nous réaliserons ceci avec les instructions pusha/pushf ; voici pour information l’état de la pile lors d’une interruption :

EtatStackHandlerInterruption

Maintenant pour réaliser cette fonction et bien il faut être au courant de certaines choses ; lorsque vous appuyez sur une touche ce déclenche une interruption, mais lorsque vous la relâchez et bien une autre interruption en générée. C’est ici qu’on fera une disctinction entre un « makecode » c’est une donné portant l’information relative à une touche préssé, et au contraire un « breakcode » lorsque la touche sera relachez. L’élément qui differencie les deux est qu’un makecode aura le bit n°7 à 0 alors que le breakcode l’aura a 1 ; on peut dors et déjà définir une macro :

//Si le scancode a le bit n°7 (on commence à 0) à 1 c'est un break code, sinan un make code
#define ToucheEnfonce(x) (x > 0x80) //Si touche enfoncé scancode = make code, si la touche est relaché scancode = break code = make code + 0x80

Il faut bien sur récupérer cet octet en dialoguant avec le pic 8042 c’est celui qui gère les évèvenements relatif au clavier ; on y accède au port n+ 0x60 et nous y accederons grâce à l’instruction asm in et encore une fois encapsulé dans une macro c :

//lit un octet sur un port
#define LecturePort(port, var) asm volatile ("inb %%dx, %%al" : "=a" (var) : "d" (port))

Nous y sommes presque, il nous reste plus qu’à réaliser la traduction du make/breakcode en touche ascii ; nous allons prendre ce code comme un indice dans un tableau contenant les touches ascii. Par exemple, le code de la touche backspace(chez moi) est 0xE et à l’indice 0xE du tableau nous avons : ‘\b’. J’ai moi même fait le tableau moi même car ceux que j’ai pu trouver sur internet ne correspondait pas du tout à mon clavier. Mon implémentation actuel gère les combinaisons de touches lshift+key/rshift+key et ctrl+alt+key, pour gérer ce genre de combinaisons il faut avoir des variables qui nous indiquent l’état de la touche avant la pression actuel ; nous realiserons cela avec quelques variables statiques. Voici mon code, il se contente d’afficher les lettres que vous pressez sur le clavier :

//Il nous faut un scancode indiquant que la combinaisons realisé avec la touche ne produit aucun affichage
#define SANSAFFICHAGE 0
 
char kbdmap[] = {
0xFF, 0xFF, 0xFF,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,    //Echap (0x01)
'&', '1', SANSAFFICHAGE,
'é', '2', '~',
'"', '3', '#',
'\'', '4', '{',
'(', '5', '[',
'-', '6', '|',
'è', '7', '`',
'_', '8', '\\',
'ç', '9', '^',
'à', '0', '@',
')', '°', ']',
'=', '+', '}',
'\b', '\b', SANSAFFICHAGE,    //backspace (0xE)
'\t', '\t', '\t',    //tab (0xF)
'a', 'A', 'a',
'z', 'Z', 'z',
'e', 'E', 'e',
'r', 'R', 'r',
't', 'T', 't',
'y', 'Y', 'y',
'u', 'U', 'u',
'i', 'I', 'i',
'o', 'O', 'o',
'p', 'P', 'p',
'^', '¨', '^',
'$', '£', '$',
'\n', '\n', '\n',//Enter (0x1C)
SANSAFFICHAGE,SANSAFFICHAGE, SANSAFFICHAGE,//Ctrl (0x1D)
'q', 'Q', 'q',
's', 'S', 's',
'd', 'D', 'd',
'f', 'F', 'f',
'g', 'G', 'g',
'h', 'H', 'h',
'j', 'J', 'j',
'k', 'K', 'k',
'l', 'L', 'l',
'm', 'M', 'm',
'ù', '%', SANSAFFICHAGE,
'²', SANSAFFICHAGE, SANSAFFICHAGE,    //²
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //LShift (0x2a)
'*', 'µ', SANSAFFICHAGE,
'w', 'W', 'w',
'x', 'X', 'x',
'c', 'C', 'c',
'v', 'V', 'v',
'b', 'B', 'b',
'n', 'N', 'n',
',', '?', ',',
';', '.', ';',
':', '/', ':',
'!', '§', '!',
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //Rshift (0x36)
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
' ' , ' ', ' ',  //Espace (0x39)
0xFF, 0xFF, 0xFF,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F1 (0x3B)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F2 (0x3C)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F3 (0x3D)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F4 (0x3E)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F5 (0x3F)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F6 (0x40)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F7 (0x41)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F8 (0x42)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F9 (0x43)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //F10 (0x44)
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,// Fleche oblique vers la gauche(0x47)
0xFF, 0xFF, 0xFF,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Fleche rapide vers le haut(0x49)
0xFF, 0xFF, 0xFF,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Fleche de gauche(0x4B)
0xFF, 0xFF, 0xFF,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Fleche de droite (0x4D)
0xFF, 0xFF, 0xFF,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Touche 'fin' (0x4F)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Fleche du bas (0x50)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Fleche rapide vers le bas(0x51)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Insert(0x52)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,//Suppr(0x53)
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF,
'>', '<', SANSAFFICHAGE,//<> (0x56)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //Windows (0x5B)
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE,
SANSAFFICHAGE, SANSAFFICHAGE, SANSAFFICHAGE, //Menu (0x5D)
};
 
void isrIntClavier(void)
{
//Pour nous ballader dans le tableau de caractere, on n'est oblige de prendre en compte les touches accompagné d'une autre (ctr, alt, shift etc)
unsigned char scancode, estMakeCode, estCaractereControle = 0, ajout;
static unsigned char shiftPresse = 0, altPresse = 0, ctrlPresse = 0;
CARACTERE ch;
CHAINE chaine;
ENTIER ent;
 
chaine.attribut = forgeCouleurClair(Rouge);
ent.attribut = forgeCouleurClair(Blanc);
 
//On récupère le scancode de la touche
LecturePort(PortLectureOuTransmissionDonnees, scancode);
 
//Si c'est un makecode
estMakeCode = ToucheEnfonce(scancode);
 
if(estMakeCode == 0)
//breakcode = makecode + 0x80
scancode -= 0x80;
 
ent.entier = scancode;
 
//On récupere les touches spécial appuyé
switch(scancode)
{
case RSHIFTKEY:
case LSHIFTKEY:
if(estMakeCode)
{
//La touche est bel et bien appuyé
shiftPresse = 1;
//C'est un caractère de "controle" nous ne devons pas l'afficher
estCaractereControle = 1;
}
else
//La touche est relaché
shiftPresse = 0;
break;
 
case CTRLKEY:
if(estMakeCode)
{
ctrlPresse = 1;
estCaractereControle = 1;
}
else
ctrlPresse = 0;
break;
 
case ALTKEY:
if(estMakeCode)
{
altPresse = 1;
estCaractereControle = 1;
}
else
altPresse = 0;
break;
}
 
if(estMakeCode && estCaractereControle == 0)    //c'est un makecode, et pas une touche de controle
{
//Je considere ici que deux combinaisons possible, shift + letter (=>min/maj), et ctrl+alt+letter (=>char spéciaux)
//Si on fait un shift + letter
if(shiftPresse)
//C'est la colonne n°2 du tableau qui nous intéresse
ajout = 1;
else if(ctrlPresse && altPresse)
ajout = 2;
else
ajout = 0;
 
ch.caractere = kbdmap[scancode*3 + ajout];
ch.attribut  = forgeCouleurClair(Blanc);
 
//Si le caractere peut etre imprimé
if(ch.caractere != SANSAFFICHAGE)
afficheUnCaractere(&ch);
}
 
return;
}

Si vous avez de plus amples détails concernant le clavier je vous conseil vivement La bible Pc, j’y ai moi même trouvé de croustillantes informations.

Et bien nous voilà arriver à la fin, le système est capable d’afficher les touches que vous frappez sur votre clavier, il est aussi capable de traiter d’autres interruptions en effet il suffit de créer un nouveau handler..Nous verrons par exemple un bout de code concernant le #PF Handler dans un prochaine article :p.

Pour finir un ptit screenshot de la bête, en espérant que vous avez aimé :], et un gr34tz à Ivanlef0u :

Hydropon1k

7 commentaires pour le moment

Ajoutez votre commentaire
  1. Très intéressant, bon courage pour la suite :)

  2. Hehe, merci pour ces explications claires :)

  3. La suite ou je te tue.

    _Geo_

  4. Il semble y avoir quelques problèmes dans le formatage de ton code, les , etc renvoient < > and co.

  5. Yep, j’avais reformuler une phrase et j’avais oublier de mettre à jour les <>& :].
    Cordialement, 0vercl0k.

  6. Tient, interessant tout ça, merci.

  7. […] 0vercl0k’s w0rld. This entry was posted in Code and tagged 0vercl0k’s, Hydropon1K, Operating, part, SYSTEM. Bookmark the permalink. ← probleme injection […]

Get Adobe Flash player