[an error occurred while processing this directive]
diff logo Informatica e sistemi alternativi
su questo sito sul Web
    Home   Chi siamo    Contattaci    Scrivi per diff    Proponi un argomento 23/04/24
    Cos'è diff    Come accedere    F.A.Q.    Promuovi    Dicono di diff    Amici di diff    
AmigaOS
Linux
FreeBSD
BeOS
OpenSource
Java
Database
Informatica
Hardware
E-Commerce
Narrativa

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.




Andrea Vallinotto
Studente di Informatica all'Universtità degli Studi di Torino.
Utente e sviluppatore Amiga, programma, quando ha tempo, in C e ARexx.
Collabora come free-lance (senza sapere dove poi atterra) alla rivista francese AmigaNews.
Altri hobby: subacquea e viaggi.

Puoi contattare l'autore scrivendo a:
avallino@diff.org


Articoli dello stesso autore:
Annuncio AmigaOS 5 (DevCon '98)
Reportage dalla fiera di Colonia '98
Clickboom
Signorina, mi scusi lo SCSI
Introduzione alla programmazione
AmigaOS 3.5 è realtà
Utility ed inutilities per la rete e per Internet
I Love You: cronache del dopobomba
I Love You ed i sistemi nostrani


 


  Indice dell'articolo:
Introduzione
Parte 1
Parte 2

Documenti collegati all'articolo:
scsidisk.h
send_scsi_cmd.h
send_scsi_cmd.c
scsi/test_unit_ready.c
<commands.h>
<sense_codes.h>
<status.h>
inquiry.c
getd.c

Link esterni citati:
la Home Page del TX310, cioè il comitato che definisce lo SCSI-3

Per informazioni circa il bus SCSI si rimanda all'articolo già pubblicato sul primo numero Signorina, mi scusi lo SCSI.


© 1999,2000,2001,2002 NonSoLoSoft di Ferruccio Zamuner (Italia)- tutti i diritti sono riservati