Die Kopfstruktur struct sg_header
dient als Übergabeschnittstelle
zwischen Applikation und Kerneltreiber.
Jetzt kommen wir zu den einzelnen Komponenten dieser Struktur.
definiert die Größe des an den Treiber gesendeten Blocks. Das Feld wird vom Kernel zur internen Verwaltung gefüllt.
definiert die Größe des erwarteten Antwortblocks. Das Feld wird von der Applikation bestimmt.
Dieses Feld dient zum Wiederfinden einer Antwort zur gestarteten Anfrage. Die Applikation kann einen neuen ID für jedes Kommando angeben. Die Antworten sind dann den Kommandos zuzuordnen, auch wenn sie in anderer Reihenfolge erscheinen. Bis zu SG_MAX_QUEUE (z.B. 4) parallele Kommandos sind möglich.
Der Rückgabewert eines read
oder write
Aufrufs.
Dieses Feld sollte vom Kernel definiert werden (leider nicht immer).
Daher ist es am besten, das Feld vor den write
Aufrufen explizit
auf Null zu setzen. Die Codes sind in errno.h
aufgeführt (0 bedeutet
kein Fehler).
Das Feld wird nur bei Nichtstandardbefehlen
(vendor specific Kommandos) im Bereich 0xc0 - 0xff ausgewertet.
Wenn Kommandos aus diesem Bereich eine Länge von 12 Bytes statt 10 Bytes
(ohne eventuelle Daten) haben,
muß dieses Feld vor dem write
Aufruf auf Eins gesetzt werden. Andere
Längen werden nicht unterstützt. Dieses Feld wird von der Applikation
gefüllt.
Dieser Puffer wird nach
Abarbeitung eines Kommandos (read()
Aufruf) vom Kernel gefüllt und
enthält den sogenannten SCSI sense code.
Manche SCSI Kommandoergebnisse stehen hier (z.B. die des TESTUNITREADY
Kommandos). Ansonsten enthält der Puffer Nullen.
Die folgende Beispielfunktion stellt den Kontakt mit dem Interface her.
Erst wird die Kopfstruktur definiert, dann der Befehl per write
geschickt, das Ergebnis per read
abgeholt und Fehlerstati gecheckt.
Der Sensepuffer ist im Ausgabepuffer untergebracht, es sei denn ein
NULL-Zeiger wurde angegeben, dann wird der Eingabepuffer verwendet.
Wir werden ihn in einem der Beispiele verwenden.
Hinweis: Setze den Wert für DEVICE
auf einen Deiner
Gerätedeskriptoren.
#define DEVICE "/dev/sgc"
/* Beispielprogramm zur Demonstration des generischen SCSI-Interface */
#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]; /* SCSI Kommandopuffer */
int fd; /* SCSI device/file-Deskriptor */
/* einen kompletten SCSI Befehl abarbeiten. Verwende das generische
SCSI-Interface. */
static int handle_SCSI_cmd(unsigned cmd_len, /* Kommandolänge */
unsigned in_size, /* Eingabedatengröße */
unsigned char *i_buff, /* Eingabepuffer */
unsigned out_size, /* Ausgabedatengröße */
unsigned char *o_buff /* Ausgabepuffer */
)
{
int status = 0;
struct sg_header *sg_hd;
/* Sicherheitsüberprüfungen */
if (!cmd_len) return -1; /* erfordert cmd_len != 0 */
if (!i_buff) return -1; /* erfordert Eingabepuffer != 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; /* kein Ausgabepuffer, keine
Ausgabegröße */
/* Aufbau der generischen SCSI-Device Kopfstruktur */
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; /* nicht notwendig */
sg_hd->pack_id; /* unbenutzt */
sg_hd->other_flags; /* unbenutzt */
#endif
/* Befehl schicken */
status = write( fd, i_buff, SCSI_OFF + cmd_len + in_size );
if ( status < 0 || status != SCSI_OFF + cmd_len + in_size ||
sg_hd->result ) {
/* ein Fehler ist aufgetreten */
fprintf( stderr, "write(generic) result = 0x%x cmd = 0x%x\n",
sg_hd->result, i_buff[SCSI_OFF] );
perror("");
return status;
}
if (!o_buff) o_buff = i_buff; /* Pufferpointer checken */
sg_hd = (struct sg_header *) o_buff;
/* Ergebnis abholen */
status = read( fd, o_buff, SCSI_OFF + out_size);
if ( status < 0 || status != SCSI_OFF + out_size || sg_hd->result ) {
/* ein Fehler ist aufgetreten */
fprintf( stderr, "read(generic) status = 0x%x, result = 0x%x, "
"cmd = 0x%x\n",
status, sg_hd->result, o_buff[SCSI_OFF] );
fprintf( stderr, "read(generic) sense "
"%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("");
}
/* Nachsehen, ob wir bekommen haben, was wir wollten */
if (status == SCSI_OFF + out_size) status = 0; /* alles bekommen */
return status; /* 0 bedeutet kein Fehler */
}
Auf den ersten Blick mag dies abschreckend wirken, jedoch dient der meiste Code zur Überprüfung und zum Melden von Fehlern. Das ist nützlich, selbst wenn der Code fehlerfrei gemacht wurde.
Handle_SCSI_cmd
hat eine generalisierte Form für alle Arten von
SCSI Kommandos, was den Transfer von Daten angeht. Es gibt diese Kategorien:
Datenmodus | Beispiel Kommando
=========================================================
weder Eingabe- noch Ausgabe-Daten | test unit ready
keine Eingabedaten, aber Ausgabedaten | inquiry, read
Eingabedaten, aber keine Ausgabedaten | mode select, write
Eingabedaten und Ausgabedaten | mode sense