La programmazione sotto SCSI di Andrea Vallinotto
Tra le nozioni fondamentali necessarie a programmare lo SCSI ad alto livello,
è importante il concetto di fase. Il bus SCSI (come molti altri) è
multiplexato, cioè ha diverse 'fasi': alcune connessioni fisiche (di solito le
linee dati) hanno un significato diverso a seconda dello stato delle linee di
controllo. Le fasi del bus SCSI sono varie (otto per il "parallel"), ma da un
punto di vista della programmazione a livello medio-alto quelle che interessano
sono tre:
- Command
-
Durante la fase comandi viene trasferito verso il dispositivo un blocco di al più
16 bytes, che specifica quale azione va effettuata.
- Data-In/Out
-
La fase dati è quella che può durare più di tutte le altre e - indovinate un po'
- in questa fase vengono trasferiti i dati, in una delle due direzioni
possibili.
- Status
-
L'ultima fase ritorna lo stato del comando inviato: 0 significa che tutto è
andato per il verso giusto, altrimenti il valore (costutito da un solo byte) darà una
prima indicazione di cosa è andato storto. La descrizione dettagliata di questi
codici si trova nel capitolo 5, sezione 2 del SAM (SCSI Architecture Model).
Comunque, di solito la condizione di errore vera e propria è CHECK_CONDITION,
corrispondente al valore 1.
Molte interfaccie software consentono inoltre di specificare un flag chiamato
Autosensing o AutoSense (ad esempio il temibile ASPI o il più mansueto SCSICMD
di Amiga). Questo flag viene utile solo quando un comando ritorna uno stato
diverso da 0: l'operazione che un programmatore assennato farebbe di solito
sarebbe quella di verificare la natura dell'errore, più in dettaglio di un
semplice codice numerico.
Questo viene effettuato mandando un altro comando (con relativa fase dati)
denominato REQUEST_SENSE . Bisogna tenere a mente che questo è un comando come
tutti gli altri, solo che entra in gioco di solito quando un precedente comando
è fallito. Il flag mezionato si occupa di eseguire quest'operazione in maniera
automatica e soprattutto atomica. L'atomicità è assolutamente indispensabile:
in un sistema decente (e quindi multitasking preemptive) potrebbe accadere che un nostro
comando fallisca e nel tempo che stiamo mandando il REQUEST_SENSE il
task-switching ha dato la CPU ad un altro processo, che a sua volta potrebbe
dialogare con lo stesso dispositivo: sarebbero così perse le tracce dell'errore.
Quindi per un corretto uso dei comandi SCSI sono necessari:
- un buffer per il trasferimento dei dati veri e propri, di dimensioni
variabili, anche molto grande;
- un piccolo buffer per il comando vero e proprio;
- un terzo buffer, che viene eventualmente usato dall' Autosensing per immaganizzare i dati
della diagnostica in caso di errore. Questo buffer è comunque limitato, al
massimo sono necessari 254 bytes (o 252 se si vuole rimanere allineati a 32
bit).
In alcune interfaccie inoltre, è necessario specificare quale direzione hanno i
dati durante la fase dati: questo perché la maggior parte dei controller SCSI
sono DMA, quindi l'hardware deve spesso configurarsi per il trasferimeto verso
la memoria centrale. Quest'informazione la si trova specificata nei testi dello
standard, a volte in maniera esplicita, più spesso in maniera implicita.
Nota per gli Amighisti: alcuni host adapter (come i GVP 2008 o 4008 con le ROM GVP)
sono in grado di piantare completamente l'hardware dell'intera macchina se
questo parametro è scorretto. |
I comandi SCSI seguono una sintassi per lo più comune e come già detto hanno una
dimensione massima di 16 bytes. Le dimensioni sono fisse: sono 6, 10, 12 e 16
bytes. A ciascuna dimensione corrisponde un generico CDB, meglio noto come
Command Descriptor Block.
Qui di seguito ci sono i 4 CDB generici.
Va notato che la dimensione dei CDB ha subito un'evoluzione storica: all'inizio erano usati
solo i CDB da 6 e 10 bytes, poi sono stati creati quelli da 12 ed infine i
più lunghi da 16 (comparsi nello SCSI-3).
Attenzione: come già detto nella prima parte, lo SCSI è di
sua natura Big-Endian. Questo significa che tutti i campi dei CDB che richiedono
più di un byte sono disposti con il byte più significativo per primo. Quindi
coloro che lavorano su un'architettura Little-endian (tutti i vari x86)
dovranno preoccuparsi di 'girare' i bytes in maniera opportuna. Coloro che hanno
un'architettura Big-endian (come i 680x0, PPC, Alpha) hanno però poco di cui bullarsi,
perché come appare evidente, poche volte i campi a più bytes sono allineati. Ad
esempio il LBA (Logic Block Address) del CDB da 10 bytes, comincia al byte 2 (16 bit)
ma è un campo da 32 bit. In genere conviene scrivere un byte per volta, facendo
gli opportuni shift; in questo modo si resta anche portabili a livello di sorgente C.
Ci scusiamo ma le immagini relative al CDB 6, 10, 12 e 16, saranno pubblicate a metà ottobre. |
Come si vede, hanno una struttura comune: il primo byte è sempre utilizzato per
definire il comando; si hanno quindi 256 possibili comandi.
Questo non è
limitante, perché attraverso le opzioni specificabili nel resto del CDB
si può ottenere tutto quello che si vuole, ed anche di più.
Al momento
attuale lo spazio dei comandi (da 0 a 255) è utilizzato per meno della metà.
Inoltre accade che lo stesso comando sia usato in maniera diversa per tipi di dispositivi
diversi: ad esempio FORMAT_UNIT per gli harddisk effettua la formattazione a
basso livello, per i CD-RW cancella le sessioni.
Il campo reserved del secondo byte (il numero 1) era utilizzato da
alcuni dispositivi SCSI-1 per specificare il Logical Unit Number (LUN) con cui
si vuole dialogare.
L'uso di questi bit era sconsigliato nello SCSI-2,
e con la terza revisione dello standard sono dati come reserved perché la gestione
delle LUN (e la definizione stessa) è più estesa, usando 64 bit.
È quindi compito del device driver gestire la selezione della sotto-unità corretta;
questo su Amiga viene fatto tramite il numero di unità passato all'apertura del
device, secondo una sintassi particolare.
Un altro campo di solito presente è il Logical Block Address; qui si
specifica l'indirizzo logico del blocco al quale si vuole accedere (non si
sarebbe detto...).
Il terzo campo è quasi sempre presente: prende nomi vari, Tranfer
length, Parameter list length, Allocation length.
Esso dichiara la lunghezza del buffer dati e molti software SCSI di basso livello si
preoccupano di controllare che questo valore corrisponda con la lunghezza
effettiva del buffer passato o allocato.
Ha tre diversi nomi perché può ricoprire tre
diverse funzioni: Tranfer length serve quando ci sono dei dati in uscita (verso
il dispositivo), Parameter list length è usato sempre in uscita, ma quando il
buffer dati è costituito da parametri addizionali, di lunghezza variabile.
L'ultimo è invece per i dati in entrata.
Proprio dall'attenta lettura di questo campo in ogni singolo CDB si può capire
quale direzione prende la fase dati.
Attenzione però: alcuni comandi molto
semplici (come TEST_UNIT_READY) non hanno alcuna fase dati.
Inoltre, quasi tutti
i comandi che ne prevedono una, consentono che il valore di questo campo sia 0, cioè che
non venga trasferito nemmeno un bit.
Può essere utile per testare quali comandi
supporti un certo dispositivo, senza combinare danni.
L'ultimo campo dei CDB, Control, viene utilizzato per i Linked
Commands ed altri usi particolari.
Di solito sono usati da chi scrive driver di
basso livello, ed ha esigenze particolari.
Negli esempi che seguono sarà sempre a 0,
quindi noi non ci faremo nulla, con Control.
L'include che invitiamo ad eseminare definisce la struttura utilizzata su Amiga per l'IO specifico SCSI.
Eccola in dettaglio:
scsidisk.h
Si possono individuare quattro diverse sezioni nella struttura: le prime tre a
carattere comune, riguardanti i tre buffer già menzionati: Data, Command, e
Sense (quest'ultimo solo se ci sono errori).
Per ognuna di esse si passa
l'indirizzo, la dimensione; in uscita si avrà nel relativo campo 'Actual' la
dimensione effettivamente trasferita.
Questo valore è molto importante: può
capitare che vengano richiesti più dati di quanti il dispositivo ne possa
trasferire (per svariati motivi); tramite 'Actual' si conosce esattamente quanti
dati del rispettivo buffer sono stati trasferiti.
Per una lettura, è indispensabile.
Al di fuori di questi tre grossi gruppi stanno scsi_Flags e scsi_Status. Il
primo va impostato con la direzione dei dati (READ o WRITE) e le opzioni per il
Sense. Il secondo riporterà lo stato del comando in uscita dalla chiamata.
Per eseguire il comando, normalmente si usa una chiamata alla funzione DoIO() di
Exec, passando una struttura IOStdReq per puntatore: i campi vanno impostati
come spiegato dall'include.
Conviene ovviamente costruirsi una funzione che faccia tutti gli opportuni
aggiustamenti e qualche struttura dati.
Per questo secondo argomento va
spesa qualche parola in più: per passare il buffer di comandi o leggere i dati
di Sense si potrebbero definire le opportune strutture; esiste però un problema
di portabilità a livello di compilatore.
Ad esempio un CDB da 10 bytes potrebbe essere definito come:
struct CDB_10
{
UBYTE op_code;
UBYTE service_action;
ULONG LBA;
UBYTE reserved;
UBYTE length1;
UWORD length2;
UBYTE control;
};
In questo esempio si notano molto bene i due problemi di byte-ordering ed
allineamento già citati.
Ad esempio, un compilatore come il GCC non esiterebbe
ad allineare il campo LBA a 32 bit (anche se certe architetture permetterebbero
quest'indirizzamento).
Si può procedere quindi in due modi: o si definiscono
delle strutture specifiche per ogni singolo comando, controllando di volta in
volta l'allineamento dei campi, oppure si usa una struttura fatta di soli bytes.
Ancora meglio, si può utilizzare un vettore di bytes, ed accedervi ad un
elemento per volta. Questo mette al riparo da qualunque differenza
architetturale tra CPU e SCSI, anche se non è molto elegante.
Insomma, non esiste una ricetta sicura. Inoltre, sono sconsigliabili
le strutture con bitfields, almeno se si vuole restare portabili: questo sempre
a causa del byte-ordering.
Ecco qui una possibile implementazione di una funzione che faccia al caso nostro.
send_scsi_cmd.h
I parametri sono:
- cmdbuffer
- Il buffer per il comando: 6, 10, 12 o 16 bytes.
- databuffer
- Il buffer dati. Meglio se allineato a 16 o 32 bits.
- sensebuffer
- Il buffer per il Sense. La dimensione massima è di 254 bytes,
perché il campo ALLOCATION LENGTH di REQUEST_SENSE è di un byte,
quindi il valore massimo è 255. Comunque, nessun dispositivo
restituisce così tanti bytes in risposta a tale comando. La dimensione standard
dei dati di Sense è di 18 bytes.
- LUN
- La LUN per questo comando. Di solito è 0.
Il valore restituito unisce in una WORD (16 bit) i due livelli di stato.
Il livello più esterno (gli 8 bit più alti) riguarda il sistema operativo: si hanno
errori a questo livello se ad esempio si tenta di aprire un'unità non esistente,
o si passano delle strutture illegali.
Un particolare codice d'errore, HFERR_BadStatus indica che a livello SCSI è stato riscontrato
un errore. In questo caso entra in gioco il secondo byte (quello più basso), che
riporta il codice corrispondente allo stato (fase STATUS) del bus SCSI.
Solitamente
questo valore è 2, che significa CHECK_CONDITION.
In ogni caso, se la WORD restituita non è 0, il buffer di sense conterrà i dati per la diagnostica.
Viene inoltre utilizzato il valore -1 per segnalare errori interni, come il passaggio
di parametri illegali.
Ecco il sorgente:
send_scsi_cmd.c
Primo esempio: TEST_UNIT_READY
Un primo (semplice esempio): testare la presenza di un rimuovibile.
In questo esempio si usa il comando TEST_UNIT_READY. Come spiega la
documentazione SCSI, questo è un comando a basso overhead, che tutti i
dispositivi che lo implementano devono eseguirlo velocemente, senza accesso
fisico all'unità. Qui ciò che interessa non è tanto inviare il comando, quanto
leggere il risultato che ritorna. Viene spesso utilizzato per controllare nei
rimuovibili la presenza della cartuccia (o supporto).
scsi/test_unit_ready.c
Nel sorgente, si fa anche riferimento all'include <commands.h>, insieme ad altri due include: <sense_codes.h> e
<status.h>. Sono definizioni per,
rispettivamente, i comandi, i codici di sense (quelli per la diagnostica degli
errori - vedi alla fine dell'articolo) e i valori del campo Status.
|
|
|