Les devices
Un article de GuruMed.
Gestion d'un device par Iliak
Voila un vaste sujet de discussion et d'étude ! Dans un premier temps, nous allons voir ce qu'est un device et ce qu'il apporte au système.
Sommaire |
Présentation
Un device est simplement un programme qui permet de communiquer avec le hardware (matériel) de l'Amiga. Ainsi pour jouer un bruit, utiliser le joystsick, utiliser l'imprimante, le port série, le presse papier, et bien d'autres encore, il vous faut passer par les devices. Ainsi chaque device gère son propre périphérique. De manière plus pratique, un device est un programme à qui on envois des commandes et des données, qui les interprète en les transmettant au matériel concerné en effectuant une opération d'Entrée/Sortie (I/O operation). Ce programme tourne de manière autonome par rapport au système. Une fois l'opération effectuée, le device vous renvois les informations que vous avez demandé. Ce système de communication est basé sur le système de messagerie Exec. Ainsi les devices comprennent deux types de commandes, les celles de base et celles spécifiques au device. Ainsi un READ sur le serial.device ou sur le trackdisk.device sera compris par ce device, mais cela ne veux pas dire que vous allez obtenir le même genre d'information ! En revanche, si vous utilisez une commande MOTOR du trackdisk.device sur le serial.device, vous obtiendriez une erreur.
Avant de s'attaquer l'ouverture même d'un device, recensons les principaux devices mise à notre disposition par le système:
- Contrôle le son
- Contrôle le Presse Papier
- Contrôle la sortie de texte
- Contrôle la souris et le joystick
- "Super" device qui gère le gameport.device et le keyboard.device
- Contrôle le clavier
- Contrôle la synthèse vocale
- Contrôle le port parallèle
- Contrôle l'imprimante
- Contrôle le matériel "Small Computer Standard Interface"
- Contrôle le port série
- Contrôle du temps
- Contrôle du lecteur de disquette
Voici donc les principaux device du système En fonction de votre système et de vote matériel, d'autre fichier se nommant ".device" sont dans voter répertoire "DEVS:". En effet ceux-ci gèrent un matériel spécifique. Ainsi 'ahi.device' permet de mieux gérer le son, 'x-surf.device' dans "Devs:Networks" permet de gérer la carte réseaux X-Surf...
Ouverture d'un device
Pour communiquer avec un device, vous devez dans un premier temps pourvoir votre programme d'un port de communication. Grace à celui-ci, le système peut envoyer et recevoir des informations. Une fois cela fait, vous devez initialiser un block de mémoire qui servira de "navette" entre votre programme et le device. C'est dans ce block que vous mettrez toutes les informations nécessaires pour que le device vous comprenne. En retour, cette mémoire sera rempli des informations que vous avez demandé. La dernière étape est d'ouvrir le fameux device. Une fois le travail fini, vous devez tout refermer avec afin de ne pas perdre les blocks mémoire utilisés. ATTENTION, vous devez libérer une à une les allocations dans l'ordre inverse de leur ouverture. Pour plus de détails reportez vous plus bas.
Pour établir le port de communication, vous avez besoin de la commande CreateMsgPort() qui va ouvrir un "Message Port" (MsgPort) dans votre programme en initialisant la structure suivante
struct MsgPort
{
struct Node mp_Node;
UBYTE mp_Flags;
UBYTE mp_SigBit;
void *mp_SigTask;
struct List mp_MsgList;
};
Aprés, vous devez créer une structure IORequest. Cela peut être fait de diférentes maniéres, mais la plus simple est celle de laisser au système le soin de s'en occuper. Pour cela, on fait appel à la fonction CreateIORequest(). La structure suivante sera initialisée :
struct IORequest
{
struct Message io_Message;
struct Device *io_Device; /* Pointeur vers le device */
struct Unit *io_Unit; /* Unité */
UWORD io_Command; /* Command à effectuer */
UBYTE io_Flags;
BYTE io_Error; /* Erreur */
};
Une fois cela fait, on ouvre le device avec un OpenDevice(). Nous avons dis plus haut que l'utilisation des devices se fait de manière générique, c'est à dire qu'une instruction est comprise par de multiples devices. L'ouverture d'un device est spécifique au device en revanche, ainsi, on ne va pas ouvrir le trackdisk.device de la même manière que l'input.device. Voici une explication de l'ouverture d'un device.
Erreur = OpenDevice(Nom, Unité, RequeteIO, Flags)
- Nom : Nom du device. Voir le tableau ci dessus pour les devices classiques.
- Unité : Par défaut 0. Cela est vraiment spécifique au device. Ainsi pour le trackdisk.device, ce chiffre correspondra au numéro du lecteur de disquette. Pour le timer.device, 5 untiés sont disponibles
! Renseignez vous bien sur le device avant d'utiliser l'unité.
- RequeteIO : Structure IORequest obtenue via CreatIORequest().
Attention, la aussi, certains device nécessitent des structures IORequest spécifique, tel que l'audio.device.
- Flags : Valeur à passer à certains devices qui le gère.
Par défaut NULL.
- Erreur : Code de retour. ATTENTION, l'ouverture d'un device réussi se matérialise TOUJOURS par un retour d'erreur NULL !!
Maintenant que nous savons ouvrir un device, il serait intérressant de savoir le refermer. Tout d'abord, il faut savoir que cela va toujours de pair par rapport à l'ouverture. Ainsi on ferme en premier ce que l'on a ouvert en dernier. En effet, si l'on ferme d'abord le port de communication (MsgPort) et ensuite la structure de requête (IORequest), attendez vous à avoir de la mémoire perdue ou au pire à planter le système. Dans un premier temps, on ferme le device avec CloseDevice(). Cela fait, on libère la structure IORequest avec un DeleteIORequest() et en dernier lieu on fait une DeleteMsgPort().
Utilisation d'un device
Une fois tout initialisé, il ne reste plus qu'à communiquer. Il existe 2 modes de communication :
- le mode synchcrone : Vous envoyez une commande et le device ne vous rends la main que lorsque vote ordre est fini
. La fonction est DoIO()
- Le mode asynchrone : Vous envoyez une commande, le device vous rends la main de suite et c'est à la charge du programme de revenir chercher la réponse. Ici il cohabite 2 fonctions SendIO() et BeginIO()
Voici une description de ces 3 commandes :
- DoIO() : Méthode la plus simple. Vous envoyez une commande, le device ne vous rends la main que lorsque la fonction est finie.
- SendIO() : Vous retrouvez de suite la main. En contre partie, vous devez revenir chercher la réponse à la commande. De plus, l'action peut s'effectuer bien plus tard par rapport à l'envois de l'ordre. Certains devices ne supportent pas cette commande et ne vous rendent la main que lorsque l'action est finie.
- BeginIO() : Cette fonction est un peu spéciale. En effet elle permet, si le flag IOF_QUICK est définie dans le champs io_Flags de la requête, de "sauter" des étapes. Cette fonction est équivalente
à SendIO() sauf que le champs io_Flags n'est pas vide. A réserver aux connaisseurs des devices !
Nous vous recommandons que d'utiliser les 2 premières fonctions. Pour de plus amples informations à propos de quelle fonction choisir, reportez vous au chapitre suivant.
Maintenant que nous connaissons les commandes à utiliser pour communiquer, il nous faut connaitre quoi remplir dans la structure IOStdReqt. Voici à quoi ressemble celle ci :
struct IOStdReq
{
...
struct Device *io_Device; /* Pointeur vers le device */
struct Unit *io_Unit; /* Unité */
UWORD io_Command; /* Commande */
UBYTE io_Flags; /* IOF_QUICK ou non */
BYTE io_Error; /* Erreur */
ULONG io_Actual; /* Nombre d'octet transferré */
ULONG io_Length; /* Nombre d'octet à transferer */
APTR io_Data; /* Pointeur vers la zone mémoire */
...
};
Il faut remplir le champ io_Command avec la commande à effectuer, io_Lenght avec la longueur total du nombre d'octet à transferer, io_Data avec le pointeur vers la zone mémoire ou commencent les données. Voici la liste des commandes Exec par défaut:
| CMD_CLEAR | Purge le buffer du device |
| CMD_READ | Commande de lecture |
| CMD_STOP | Arrete l'activité du device |
| CMD_FLUSH | Vide la file d'attente des commandes |
| CMD_RESET | Réinitialise un device |
| CMD_WRITE | Commande de lecture |
| CMD_INVALID | Créer une erreur |
| CMD_UPDATE | Mets à jour le device |
| CMD_START | Redemarre le device |
Reportez vous quand même à la documentation du device avant de les utiliser !
Synchrone ou Asynchrone ?
Pourquoi existe-t-il des commandes synchrones (qui ne vous rendent la main que lorsque la commande est finie) et des commandes asynchrones (qui rendent la main de suite) ? Voici un petit tableau récapitulant les avantages et les inconvénients des 2 fonctions :
| avantages | inconvénients | |
|---|---|---|
| DoIO() | Pas de vérification nécessaire | Pas de retour immédiat |
| SendIO() | Retour immédiat | Vérification nécessaire du retour de la commande |
| On peut envoyer d'autres commandes, même si on a pas reçu de message de retour (*) | On ne peut pas toucher aux données utilisées par le device tant que l'on a pas de message de retour |
(La commande BeginIO() est explicitement ignorée)
Comme vous pouvez le voir, les deux fonctions se valent. En fait, tout dépends du type d'application que vous créez.
Si vous voulez envoyer plusieurs requête au même device avec la commande SendIO(), vous devez mettre en place une certaine technique. En effet, si vous n'avez toujours pas reçue de mesasge de retour de votre précédente commande et que vous devez réutilisez ce device, vous devez alors alors créer une deuxième structure IORequest et y recopier la première structure dedans. Ainsi vous pourez envoyer plusieurs commandes en même temps au même device.
Afin de savoir quand un retour de commande est arrivé, le système nous donne deux méthodes. La première mets la tâche en attente jusqu'à ce que le device reponde. Pour cela nous pouvons utiliser WaitIO(), Wait() ou WaitPOrt(). Etant donné que la tâche attends un retour du device, il est préférable d'utiliser l'autre méthode. La seconde méthode utilise la fonction CheckIO(). Celle ci regarde juste si, par rapport à une requête, la réponse est arrivée et n'attends pas. Dans tous les cas, vous devrez manuellement enlever la rêquete de réponse de la file d'attente du port de communication.
Maintenant que nous avons une réponse du device, il faut l'analyser. Dans un premier temps, il faut vérifier que l'opération c'est bien déroulée en regardant le code d'erreur (io_error). Une valeur NULL indique un succés, en revanche une autre valeur signifie qu'une erreur est survenue durant l'exectution de la commande. Il existe deux types d'erreur, les erreurs d'Entrée/Sortie d'exec (voir ) et les erreurs spécifiques au device (voir la documentation du device). En cas d'erreur (et selon le type d'application) vous pouvez réenvoyer votre commande, car à un moment donné, il se peut trés bien que la commande échoue et la tentative suivante s'avère fructueuse (exemple l'allocation de canaux audio). Il peut s'avérer que vous désiriez quitter et annuler toutes les communications avec le device. Il ne faut surtout pas fermer le device tant que vous n'avez pas eut un message de retour de toutes vos commandes. Pour annuler une commande, utilisez la fonction AbortIO(). Cette fonction n'est pas supportée par tous les devices, donc veuillez bien lire la documentation du device. Avec cette fonction, vous n'avez qu'annullé la commande elle même, il vous reste donc à libérer tout ce qui a été alloué.
Pour vous aider, voici un petit pense bête de ce qu'il faut faire pour quitter un programme qui utilise un device :
- Annuller toutes les commandes en cours avec un AbortIO()
- Attendre le retour des commandes avec un WaitIO() (ou un autre précédement vu)
- Fermer le device avec un CloseDevice()
- Libérer la structure IORequest (ou IOStdReq) avec un DeleteIORequest()
- Effacer le port de communication avec un DeleteMsgPort()
Exemple
Maintenant, voici un petit programme qui ouvre le serial.device (port série) et demande le statut du port et affiche le résultat.
#include
#include
#include
#include
#include
int main(void)
{
struct MsgPort *SerialMP; /* Pointeur vers notre "Message Port" */
struct IOExtSer *SerialIO; /* Pointeur vers notre "I/O Request" */
/* Crée port de communication */
if (SerialMP=CreateMsgPort())
{
/* Crée la requête d'E/S */
if (SerialIO = CreateIORequest(SerialMP,sizeof(struct IOExtSer)))
{
/* Ouvre le serial.device */
if (OpenDevice(SERIALNAME,0,(struct IORequest *)SerialIO,0L))
printf("Erreur: %s n'a pu être ouvert !\n",SERIALNAME);
else
{
/* On envoie une commande */
SerialIO->IOSer.io_Command = SDCMD_QUERY;
if (DoIO((struct IORequest *)SerialIO))
{
/* La commande a raté */
printf("Query échoué. Erreur - %d\n",SerialIO->IOSer.io_Error);
}
else
{
/* On affiche le résultat */
printf("Statut du serial.device : %x\n\n",SerialIO->io_Status);
}
/* On ferme le serial.device */
CloseDevice((struct IORequest *)SerialIO);
}
/* On efface la requête d'E/S */
DeleteIORequest(SerialIO);
}
else
/* On a échoué */
printf("Erreur: Ne peut pas créer la requête d'E/SE !\n");
/* On efface le port de communication */
DeleteMsgPort(SerialMP);
}
else
/* Pas de port de communication */
printf("Erreur: Ne peut pas créer le port de communication !\n");
return(0);
}
