Up Next

1  Première couche logicielle : accès au matériel

Nous disposons d’un disque dur organisé en pistes (aussi nommées cylindres), chacune des pistes étant organisée en secteurs. La couche logicielle la plus base définit une interface C avec ce matériel. Cette interface est une forme simplifiée de la norme ATA-2 supportée par les fabricants de disque dur de type IDE (ceux installés dans des PC « standard »). La bibliothèque hardware qui constitue cette première couche vous est fournie comme point de départ sous la forme des fichiers hardware.h et libhardware.a ; voir l’encart. Cette bibliothèque permet d’émuler certains composants matériels d’un ordinateur, comme une carte ethernet ou un disque dur. Nous ne nous occuperons pour ce projet que du disque dur maître.

Le fichier hardware.h définit un jeu réduit de fonctions C qui permettent de contrôler, entre autres, l’activité du disque magnétique. Un fichier de configuration paramètre le fonctionnement du matériel émulé. Un tel fichier, nommé hw_config.ini, vous est fourni avec la bibliothèque hardware ; il contient des valeurs par défaut. Ce paramétrage vous permet d’activer ou de désactiver les différents composants matériels émulés, il est impératif, pour ce qui nous concerne ici, que le disque dur maître soit bien activé (la valeur ENABLE_HDA doit être positionnée à 1 dans le fichier de configuration).

La bibliothèque hardware définit la fonction :

int init_hardware(const char *config_file);

Cette première fonction permet d’initialiser le matériel émulé (et donc le disque dur) à partir du fichier de configuration dont le chemin d’accès est fourni en paramètre. L’appel à cette fonction met sous tension le disque et calibre position et mouvement de la tête de lecture. Après initialisation, la tête de lecture est placée sur le secteur 0, piste 0.

Si cette phase d’initialisation du matériel n’est pas effectuée, le comportement du disque n’est pas prédictible. Il faut donc appeler cette fonction avant toute autre opération en guise d’initialisation de vos programmes.

Mise en place des travaux pratiques

Pour commencer le TP il faut récupérer le répertoire tpfs sur le Git du FIL à

https://gitlab-etu.fil.univ-lille1.fr/ms1-ase/src

Vous trouverez dans ce répertoire un fichier mkhd.c (make hard disk) qui montre comment initialiser les disques. Pour la suite du TP vous n’aurez besoin que du disque « MASTER ».

Un fichier Makefile est disponible. Il compile le fichier mkhd.c avec la bibliothèque hardware. Tapez simplement make.

Notez encore que dans la bibliothèque hardware que nous vous fournissons simule le fonctionnement des primitives ATA en utilisant en guise de disque un fichier Unix nommé vdiskA.bin (option par défaut pour le disque maître) créé dans le répertoire courant. Si ce fichier n’existe pas notre simulateur de disque le recrée, dans un état non initialisé. En supprimant ce fichier vous créez donc un nouveau disque...

Un fichier est créé pour contenir les informations du disque maître. Le nom par défaut de ce fichier est vdiskA.bin. Il peut être modifié grâce au fichier de configuration.

La communication avec le matériel (envoi de commandes et de données, envoi/réception de données) se fait via des ports, soit en écriture soit en lecture. Les numéros des ports utilisés pour la communication avec le disque dur sont définis eux aussi dans le fichier de configuration. On trouve par exemple dans le fichier hw_config.ini fourni :

# Paramètres du contrôleur IDE
ENABLE_HDA      = 1            # 0 => simulation du disque désactivée
HDA_CMDREG      = 0x3F6        # registre de commande du disque maitre
HDA_DATAREGS    = 0x110        # base des registres de données (r,r+1...r+15)
HDA_IRQ         = 14           # Interruption du disque

# Paramètres de la simulation
HDA_FILENAME    = "vdiskA.bin" # nom du fichier de stockage du disque simulé

On ne peut lire ou écrire qu’un seul octet sur un port. Pour écrire une valeurs codées sur plusieurs octets, on utilise un premier port pour l’octet de poids fort et les ports suivants pour les octets de poids plus faible. De la même manière, si une fonction retourne une valeur de plusieurs octets, l’octet de poids fort sera lu sur le premier port, les octets de poids plus faibles sur les ports suivants.

La fonction

int _in(int port);

réalise la lecture sur le port désigné. La valeur retournée correspond à l’octet qui a été lu sur ce port. Les numéros de port sont identifiés dans le fichier de configuration du matériel hardware.ini. La fonction

void _out(int port, int value);

réalise l’écriture d’une valeur d’un octet sur le port désigné.

L’envoi de commandes se fait également en écrivant sur le port désigné comme port de commande dans le fichier de configuration. Cela permet au microprocesseur de solliciter une opération du disque magnétique. Une fois que le microprocesseur envoie une commande, l’exécution de celle-ci débute immédiatement. Si la commande nécessite des données, il faut obligatoirement les avoir fournies avant de déclencher la commande. De la liste des commandes ATA-2 nous avons retenu le sous-ensemble décrit dans la table 1. Le fichier harware.h définit aussi des macros identifiant ces commandes :

#define CMD_SEEK        0x02
#define CMD_READ        0x04
...

Tableau 1: Commandes ATA-2
Nomcodeport de données (P0; P1; ...; P15)objet
SEEK0x02numCyl (int16); numSec (int16)déplace la tête de lecture
READ0x04nbSec (int16)lit nbSec secteurs
WRITE0x06nbSec (int16)écrit nbSec secteurs
FORMAT0x08nbSec (int16); val (int32)initialise nbSec secteurs avec val
STATUS0x12R.F.U.R.F.U.
DMASET0x14R.F.U.R.F.U.
DSKNFO0x16nbCyl (int16); nbSec (int16); tailleSec (int16)retourne la géométrie d’un disque
MANUF0xA2Id du fabricant du disque (16 octets)Identifie le disque
DIAG0xA4statusdiagnostic du disque : 0 = KO / 1 = OK

Une fois une commande fournie au circuit ATA, celui-ci met un certain temps à la réaliser. Si une deuxième commande est passée entre temps, la première commande est « interrompue » laissant le matériel dans un état indéterminé... Pour informer le microprocesseur (et donc le système) de l’état d’avancement d’une commande le circuit ATA génère un signal d’interruption particulier.

Ce signal est émis après que la commande SEEK ait atteint la position demandée, ou pour chaque secteur lu (READ), écrit (WRITE), ou formaté (FORMAT). Enfin ce signal est émis après qu’un diagnostic complet ait été accompli, à ce moment seulement la valeur OK ou KO peut être lue dans le premier registre de données. Pour les autres commandes le résultat est immédiat.

Pour attendre que le disque ait terminé l’exécution de la commande en cours, il faut utiliser la fonction :

void _sleep(int irq_level);

Le niveau d’IRQ passé en paramètre dépend du matériel visé (ici le disque dur), ce niveau étant défini dans le fichier de configuration.

De plus, un traiteur d’interruption doit être associé à chacune des 16 IRQ (0 à 15). Un traiteur d’interruption est une fonction de type

typedef void (*func_irq_t)();

Le vecteur IRQVECTOR[16] identifie ces fonctions et se doit d’être initialisé. La fonction IRQVECTOR[n]() est appelée lorsque l’interruption de niveau n est déclenchée par le matériel.

Enfin la valeur MASTERBUFFER est un pointeur sur un unsigned char qui identifie le tampon exploité par le contrôleur de disque maître. Ce tampon est exploité par les commandes READ et WRITE pour stocker les données lues/à écrire.


Exercice 1
 (Afficher un secteur : dump sector, dmps)   Comme premier outil, nous allons concevoir un petit programme qui prend deux arguments en paramètres, un numéro de piste et un numéro de secteur et qui affiche le contenu, octet par octet, du secteur (sous forme hexadécimale par exemple) du disque maître.
Question 1   Quelle est la suite de commandes matérielles qu’il faut solliciter pour lire un secteur ?
Question 2   En supposant que les variables int cylinder et int sector contiennent respectivement le numéro de piste et de secteur à lire, expliquez les valeurs à écrire dans les ports pour désigner la position que la piste doit atteindre sur le disque.
Question 3   Réalisez le programme dmps.

Exercice 2
 (Formater un disque : frmt)   On se propose maintenant d’écrire un programme qui détruit entièrement le contenu d’un disque physique en formatant chaque secteur du disque. Proposez un tel programme.

Exercice 3
 (Une bibliothèque pour l’accès physique : drive)   Pour pouvoir simplifier notre tâche dans les développements à suivre, nous nous proposons d’écrire une première série de fonctions utilitaires qui formerons notre bibliothèque drive d’accès au périphérique.
void read_sector(unsigned int cylinder, unsigned int sector,
                 unsigned char *buffer);
void write_sector(unsigned int cylinder, unsigned int sector,
                  const unsigned char *buffer); 
void format_sector(unsigned int cylinder, unsigned int sector,
                   unsigned int nsector,
                   unsigned int value);

Première étape des travaux pratiques — Validation de la bibliothèque d’accès au matériel

Une validation minimale de la bibliothèque drive peut être obtenue en écrivant les commandes dmps et frmt au dessus de la bibliothèque. Observer attentivement le résultat de commandes telles les suivantes :

Vous pouvez penser à comparer les résultats de votre commande avec ceux produits sur le même disque par la commande de vos camarades.

Pour tester l’écriture sur le disque, il est nécessaire de développer un programme ad hoc.

Deux remarques :

  1. Il est illusoire de poursuivre les développements qui s’appuieront sur cette bibliothèque drive sans que celle-ci ait été validée.
  2. Il vous faudra rendre ou démontrer vos programmes de tests lors de l’évaluation de votre travail.


Up Next