Page suivantePage précédenteTable des matières

8. La structure d'en-tête

La structure d'en-tête struct sg_header est utilisée comme couche de contrôle entre l'application et le pilote du noyau. Abordons maintenant le détail de ses composants.

int pack_len

définit la taille du bloc envoyé au pilote. Cette valeur est définie dans le noyau pour une utilisation interne.

int reply_len

définit la taille du bloc accepté en réponse. Cette valeur est définie du côté application.

int pack_id

Ce champ facilite l'appariement des réponses aux requêtes. L'application peut fournir un identifiant unique à chaque requête. Supposons que vous ayez écrit un certain nombre de commandes (disons 4) pour un périphérique. Celles-ci peuvent fonctionner en parallèle, l'une d'entre elles étant la plus rapide. Lors de la lecture des réponses par quatre "read", celles-ci ne sont pas forcément dans l'ordre des requêtes. Pour identifier la réponse correcte pour une requête, on peut utiliser le champ pack_id. Habituellement, cette valeur est incrémentée après chaque requête (et boucle éventuellement). Le nombre maximum de requêtes émises simultanément est limité par le noyau à SG_MAX_QUEUE (en général, quatre).

int result

C'est la valeur du résultat d'un appel à read ou à write. Elle est (parfois) définie par la le pilote générique (partie noyau). Il est plus prudent de le positionner à 9 avant l'appel à write. Ces codes sont déclarés dans le fichier errno.h (0 indique un résultat correct).

unsigned int twelve_byte:1

Ce champ n'est nécessaire que lors de l'utilisation de commandes spécifiques non standard (dans la plage 0xc0 à 0xff). Lorsque la longueur de ces commandes est de 12 octets au lieu de 10, il faut positionner ce champ à 1 avant l'appel à write. D'autres longueurs de commandes ne peuvent être utilisées. Ce champ est positionné par l'application.

unsigned char sense_buffer[16]

Ce tampon est positionné après l'exécution d'une commande (après un appel à read()) et contient le code de "sensation" SCSI (SCSI send code. NdT. : dans le reste du document, on utilisera simplement la formule "tampon SCSI"). Certains résultats de commandes doivent être lus à cet emplacement (par exemple pour TESTUNITREADY). Il ne contient habituellement que des octets nuls. La valeur de ce champ est positionnée par le pilote générique (partie noyau).

L'exemple de fonction qui suit s'interface directement avec le pilote générique du noyau. Il définit la structure d'en-tête, envoie la commande par write, lit le résultat par read et effectue un nombre (limité) de contrôles d'erreurs. Les données du tampon SCSI sont disponibles dans le tampon de sortie (sauf si un pointeur nul a été fourni, auquel cas elles se trouvent dans le tampon d'entrée). Nous l'utiliserons dans les exemples qui suivent.

Note : positionnez la valeur de DEVICE à celle qui correspond à votre matériel.

#define DEVICE "/dev/sgc"
/* Programme d'exemple utilisant l'interface SCSI générique */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <scsi/sg.h>
#define SCSI_OFF sizeof(struct sg_header)
static  unsigned char cmd[SCSI_OFF + 18];       /* tampon de commande SCSI */
int     fd;                                     /*
 * descripteur de peripherique/
 * fichier SCSI
 */
/* traite une commande SCSI complète. Utilise l'interface générique */
static int handle_SCSI_cmd(unsigned cmd_len,     /* longueur de commande  */
 unsigned in_size,     /* taille data en entrée */
 unsigned char *i_buff,/* tampon d'entrée       *//
 unsigned out_size,    /* taille data en sortie */
 unsigned char *o_buff /* tampon de sortie      */
 )
{
 int status = 0;
 struct sg_header *sg_hd;
 /* vérifications de sécurité */
 if (!cmd_len) return -1;            /* nécessite que cmd_len != 0 */
 if (!i_buff) return -1;             /* nécessite que i_buff != NULL */
#ifdef SG_BIG_BUFF
 if (SCSI_OFF + cmd_len + in_size> SG_BIG_BUFF) return -1;
 if (SCSI_OFF + out_size> SG_BIG_BUFF) return -1;
#else
 if (SCSI_OFF + cmd_len + in_size> 4096) return -1;
 if (SCSI_OFF + out_size> 4096) return -1;
#endif
 if (!o_buff) out_size = 0;          /* pas de tampon de sortie, pas de */
 /* taille                          */
 /* construction de l'en-tête générique de périphérique */
 sg_hd = (struct sg_header *) i_buff;
 sg_hd->reply_len   = SCSI_OFF + out_size;
 sg_hd->twelve_byte = cmd_len == 12;
 sg_hd->result = 0;
#if     0
 sg_hd->pack_len    = SCSI_OFF + cmd_len + in_size; /* non indispensable */
 sg_hd->pack_id;     /* inutilise */
 sg_hd->other_flags; /* inutilise */
#endif
 /* envoi de la commande */
 status = write( fd, i_buff, SCSI_OFF + cmd_len + in_size );
 if ( status < 0 || status != SCSI_OFF + cmd_len + in_size ||
 sg_hd->result ) {
 /* condition d'erreur */
 fprintf( stderr, "write(generic) resultat = 0x%x cmd = 0x%x\n",
 sg_hd->result, i_buff[SCSI_OFF] );
 perror("");
 return status;
 }
 if (!o_buff) o_buff = i_buff; /* contrôle du pointeur du tampon */
 /* récupération du résultat */
 status = read( fd, o_buff, SCSI_OFF + out_size);
 if ( status < 0 || status != SCSI_OFF + out_size || sg_hd->result ) {
 /* condition d'erreur */
 fprintf( stderr, "read(generic) statut = 0x%x, resultat = 0x%x, "
 "cmd = 0x%x\n",
 status, sg_hd->result, o_buff[SCSI_OFF] );
 fprintf( stderr, "read(generic) tampon SCSI "
 "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n",
 sg_hd->sense_buffer[0],         sg_hd->sense_buffer[1],
 sg_hd->sense_buffer[2],         sg_hd->sense_buffer[3],
 sg_hd->sense_buffer[4],         sg_hd->sense_buffer[5],
 sg_hd->sense_buffer[6],         sg_hd->sense_buffer[7],
 sg_hd->sense_buffer[8],         sg_hd->sense_buffer[9],
 sg_hd->sense_buffer[10],        sg_hd->sense_buffer[11],
 sg_hd->sense_buffer[12],        sg_hd->sense_buffer[13],
 sg_hd->sense_buffer[14],        sg_hd->sense_buffer[15]);
 if (status < 0)
 perror("");
 }
 /* A-t-on ce qu'on attendait ? */
 if (status == SCSI_OFF + out_size) status = 0; /* on a tout */
 return status;  /* 0 indique que tout est bon */
}

Bien que cela puisse sembler quelque peu complexe au premier abord, une grande partie du code est dédiée aux contrôle et détection d'erreurs (ce qui est utile même une fois que le code fonctionne correctement).

Handle_SCSI_cmd présente une forme généralisée pour tous les types de commandes SCSI, qui correspondent à l'une des catégories qui suivent :

 Mode de données            |    Exemple de commande
=============================================================
ni entrée ni sortie de données  |     test d'unite prête
pas d'entrée, sortie de données |      requête, lecture
entrée de données, pas de sortie| selection de mode, écriture
 entrée et sortie de données   |     détection de mode


Page suivantePage précédenteTable des matières