Ogni periferica su Amiga e' controllata da un DRIVER, o meglio da un DEVICE che non e' altro che un TASK, costantemente eseguito da Exec, che ha il compito di convertire i comandi impartiti dall' utente, tramite il DOS, in codici comprensibili dalla periferica stessa. A questa regola non sfugge neanche il DRIVE, o meglio il Floppy Disk Driver, esso e' gestito in maniera trasparente dalla Trackdisk.Device, che si incarica di convertire i comadi posti dall' utente nel Request I/O nei codici che attivano il DMA per la lettura fisica del Floppy Disk. In questa trattazione vedremo come e' possibile in un nostro programma interaggire con la Trackdisk.Device per leggere, scrivere, formattare, copiare un Floppy Disk.
Prima di tutto il nostro TASK deve essere in grado di comunicare con il resto del sistema, quindi deve essere dotato di una Porta Messaggi, che gli consentira' di non restare isolato, il che gli impededirebbe di portare a termine il compito per il quale e' stato creato. Riassumendo, noi dobbiamo fornire al nostro programma una porta messaggi, tramite la quale potra' dialogare e comandare la Trackdisk.Device, per fare cio' basta vedere il listato qui sotto riportato. Una Message Port non e' altro che una Struttura di dati, che e' una bestia nera per i non iniziati : leggete una manciata di Bytes che contengono informazioni vitali, qualche purista della programmazione dopo tale affermazione vorra' la mia testa, (esempio tipo di Message Port nella Fig. 1), cio' che interessa per allocare una MP e' la sua lunghezza, che e' contenuta implicitamente nella Fig. 1 : MP_SIZE equ $022, ovvero la lunghezza standard fissata dalla Commodore e' di 34 bytes, quindi per poter aprire una MP bisogna avere 34 bytes contigui. Sono moltepilici i modi per ottenerli, vediamone alcuni: chiedere ad Exec di riservarci 34 Bytes, oppure accodare 34 bytes al nostro programma con le istruzioni blk.b 34,0 per il SEKA o ds.b 34 per il Devpac e gli altri assembler standard, quest' ultima soluzione sara' poco professionale, ma e' quella che ho adottato nel programma poiche' per qualche centinaio di bytes in meno nel File Oggetto non vale la pena scomodare Exec, poiche' bisognerebbe aggiungere nel sorgente le istuzioni per richiedere la memoria, effettuare i controlli per vedere se effettivamente abbiamo avuto la nostra zona libera e prendere le dovute diramazioni, e per finire restituire la memoria ad Exec quando il programma termina!
Guardiamo ora le prime righe del programma:
; Apre una porta messaggi
start: move.l 4,A6 ; ExecBase -> A6
sub.l A1,A1 ; 0 -> A1 per ricercare l' indirizzo del proprio
jsr LVOFindtask(A6) ; Task nella lista dei Tasks di Exec.
lea MSGPort(pc),A1
add.w #$0010,A1
move.l D0,(A1) ; ^Task -> MP_SIGTASK
lea MSGPort(pc),A1 ; Aggiunge alle porte del sistema la propria!
jsr LVOAddPort(A6)
Tramite la prima istruzione si pone ExecBase nel registro A6 del 68000, che ci servira' per richiamare le funzioni di Exec : FindTask, e AddPort.
La funzione FindTask necessita del passaggio, nel registro A1, di un puntatore ad una stringa BCPL (BCPL = antenato del C) terminata con un byte nullo, che contenga il NOME del Task che intendiamo ricercare, ed essa restituira' nel registro D0 il puntatore alla struttura di controllo di tale Task, sempre che tale Task esista, altrimenti D0 sara' nullo. Se A1 e' nullo, come in questo caso, essa restituira' il puntatore alla struttura di controllo del Task attualmete in esecuzione, e cioe' il NOSTRO. Tale puntatore va subito annotato nella nostra MP, e piu' precisamente in MP_SIGTASK, dopo di che possiamo chiamare la funzione AddPort di Exec, che si occupera' di organizzare la nostra MP e di aggiungerla nella lista delle MP del sistema.
La funzione AddPort necessita del passaggio, nel registro A1, di un puntatore alla struttura dati della nuova MP, e non restituisce alcun parametro.
A questo punto la nostra MP e' bella e pronta, e costantemete tenuta sottocontrollo da Exec.
Aperta la porta messaggi non ci resta che organizzare la struttura dati REQUEST I/O, necessaria per pilotare la Trackdisk.Device, per rendervi conto di come sia organizzata una struttura dati di questo tipo guardate la Fig. 2. Come i piu' attenti di voi avranno certamente notato vi sono due differenti tipi di Request I/O : la standard (32 bytes), e l' estesa (48 bytes). Sorge spontaneo il seguente dubbio : quale usare ? Se non si e' taccagni si sceglie subito la 2°, che in ogni caso ci metterebbe al riparo da eventuali Overflow di dati, ma le cose non stanno cosi', perche' anche la Request I/O Estesa e' insufficiente per il nostro scopo, difatti la Trackdisk necessita di un' ulteriore estensione riportata nella Fig. 3, che porta cosi' definitivamente la Request I/O alla lunghezza di 56 bytes.
; Apre il Trackdisk.Device
lea ExtRequestIO(pc),A1
move.l #0,D0 ; Select which DRIVE
clr.l D1 ; DF0: DF1: DF2: DF3:
; D0 = 0 1 2 3
lea Trackdisk(pc),A0
jsr LVOOpenDevice(A6)
lea ExtRequestIO(pc),A1
lea MSGPort(pc),A5
move.l A5,14(A1) ; ^MSGPort -> IO_DEVICE
In questo secondo gruppo di istruzioni si fa uso di un' altra funzione di Exec, e piu' precisamente della OpenDevice, questa funzione necessita di ben tre diversi parametri, e sara' compito nostro passarglieli tramite i registri A0,A1,D0. In A1 deve essere contenuto il puntatore alla nostra Request I/O, in A0 il puntatore ad una stringa BCPL contenete il nome del DEVICE che intendiamo chiamare, anche in questo caso la stringa deve essere terminata con un byte nullo, e in D0 il numero dell' unita' logica sulla quale vogliamo operare, per il valore da inserire in D0 basta vedere i commenti. La OpenDevice provvedera' ad organizzare correttamente la struttura dati, noi dobbiamo occuparci di legare la MP con la ExtRequestIO, inserendo il puntatore alla MP nella IO_DEVICE della ExtRequestIO, a questo punto il tutto e' pronto, possiamo sbizzarrirci a comadare il DF0:, tutti i comandi sono riportati nella Fig. 4.
Vediamo come si inviano alla Trackdisk i comandi.
Nella Fig. 2 sono riportate le cinque voci: IO_ACTUAL, IO_LENGTH, IO_DATA, IO_OFFSET, IO_COMMAND, di queste solo le ultime quattro servono per operare, mentre la prima ci restituira' dopo ogni operazione il CODICE DI ERRORE, che dovra' essere interpretato secondo la tabella pubblicata nella Fig. 5, vediamone in dettaglio il loro significato.
Nella IO_LENGTH (Hex = $0024) va messa la lunghezza del buffer di dati che si vogliono inviare o ricevere dal disco, e ha le dimensioni di una Longword (32 Bits).
Nella IO_DATA (Hex = $0028) va inserito il puntatore al buffer di dati interessato dalla nostra operazione, anche in questo caso si tratta di una Longword.
Nella IO_OFFSET (Hex = $002C) va inserito l' OFFSET, ovvero la posizione dalla quale cominciare l' operazione sul disco, anche tale parametro ha la lunghezza di una Longword. Per calcolare correttamente l' OFFSET bisogna tener conto delle seguenti caratteristiche dei Floppy Disk dell' Amiga:
- Ogni disco viene formattato in 80 CILINDRI (0-79).
- Ogni cilindro e' suddiviso in due tracce, una per ogni facciata (0-159).
- Ogni traccia e' suddivisa ulteriormente in 11 settori uguali (0-1759).
- Ogni settore contiene ben 512 bytes.
- In totale 80 * 2 * 11 * 512 = 901120 bytes = 880 Kbytes.
La piu' piccola unita' sulla quale e' possibile lavorare con la Trackdisk, nonostante venga letta o scritta l' intera traccia, e' il settore o blocco, per forza di cose l' OFFSET dovra essere un multiplo di 512, quindi si giunge alla seguente formula : OFFSET = Num.Blocco * 512. Facciamo delle prove.
Volendo leggere il BOOTBLOCK (blocchi 0 e 1 di ogni ogni disco) dovremmo inserire nella ExtRequestIO i seguenti valori:
IO_OFFSET = 0 * 512 = 0
IO_LENGTH = 2 * 512 bytes = 1024 bytes
IO_DATA = ^BUFFER_DEI_DATI
Se invece per leggere la Root:
IO_OFFSET = 880 * 512 = $6E000
IO_LENGTH = 1 * 512 bytes = 512 bytes
IO_DATA = ^BUFFER_DEI_DATI
In conclusione l' OFFSET ha il seguente range : $00000 <= OFFSET <= $DC000, purche' sia sempre un multiplo di 512 o $0200.
Come avrete certamente intuito nella IO_COMMAND dovra essere inserito il codice comando estratto dalla Fig. 4, cio' caratterizza la IO_COMMAND dalle altre su viste e' che questa volta siamo in presenza di una Word e non di una Longword, quindi la MOVE dovra essere una MOVE.W, spesso tale errore e' fonte del seguente bug, in apparenza inspiegabile. La Move.L #2,IO_COMMAND scrivera' una Longword in IO_COMMAND, che quindi conterra' il valore $0000 in luogo del voluto $0002, mentre il valore $0002 verra' posto nelle successive IO_FLAGS e IO_ERROR, entrambe hanno la lunghezza di un byte. Per questo motivo bisogna prestare molta attenzione quando si usano delle STRUTTURE DATI, la piu' piccola distrazione in fase di scrittura di un programma ci puo' portare a fare degli incotri spiacevoli con il Guru, o ancora peggio a perdite di dati sui supporti magnetici.
Una volta posizionati i dati nella ExtRequestIO, dobbiamo informare il sistema che siamo in attesa di un' operazione di I/O con una periferica, per fare cio' ci serviamo di un' altra funzione di Exec la DOIO.
Questa funzione necessita del puntatore alla RequestIO, che deve essere posizionato nel registro A1, se diamo uno sguardo al listato si vede ho usato proprio tale registro per puntare alla ExtRequestIO, quindi non e' stata una scelta casuale dettata dalla perfetta equivalenza tra i sette registri indirizzo del 68000, ma e' sfrutto di una attenta programmazione per l' ottimizzazione del sorgente.
A dire la verita' in luogo della DoIO, possiamo anche utilizzare la SendIO, un' altra funzione di Exec per svolgere le operazioni di I/O, ma io preferisco in questi casi sfruttare la DoIO, poiche' la DoIO interrompe il flusso del nostro programma finche' l' operazione non si e' conclusa, mentre la SendIO attiva l' operazione di I/O e torna al programma, che grazie al multitasking sara' eseguito in parallelo con la I/O, a questo punto dovremmo ciclicamente controllare se la I/O e' terminata prima di elaborare i dati, indubbiamente in questo caso e' da evitare, ma in altre circostanze potrebbe risultare una necessita'.
Una volta terminata la nostra necessita' di dialogare con la trackdisk dobbiano, prima di uscire dal programma, chiudere la MP e la Request I/O, altrimenti si incorrera in una sicura Guru Meditation, per fare cio' sono sufficienti le poche righe seguenti:
lea MSGPort(pc),A1 ; Chiude la porta messaggi
jsr LVORemPort(a6)
lea ExtRequestIO(pc),A1 ; Chiude la Trackdisk.device
jsr LVOCloseDevice(A6)
Un'altra importante regola, quando si scrive un programma su Amiga, e' la seguente : all' uscita dal programma il registro dati D0 deve essere nullo se l' operazione si e' svolta correttamente o se non si vogliono visualizzare errori, altrimenti dovra contenere un Return Code che sara' interpretato dal Dos che provvedera' ad informare, nel modo piu' approriato l' utente. Quindi per non generare inutili segnalazioni e' consigliabile azzerare tale registo con una delle possibili istuzioni: Moveq.l #0,D0 , Sub.l D0,D0 , Clr.l D0 ...
A questo punto non ci resta che passare in rassegna i comandi della Trackdisk e spiegare come utilizzarli al meglio.
READ
Tramite questo comando possiamo trasferire dal supporto magnetico nella memoria tutti i dati che ci interessano, non necessita di ulterioni spiegazioni.
WRITE
Il comando Write, diversamente da quello che il suo nome puo' far pensare, non scrive i dati sul disco, ma informa la Trackdisk sulle nostre intenzioni e le passa tutte le informazioni per poterlo fare. La scrittura vera e propria avviene automaticamente prima del successivo comando Read nello stesso buffer o per mezzo di una nostra successiva conferma (vedi Update).
UPDATE
Questo comando ci permette effettivamente di scrivere in maniera definitiva i dati sul supporto magnetico. Tutto cio' puo' sembrare a prima vista molto macchinoso ed inutile, ma non e' cosi'. La Commodore ha scelto questo sistema per due motivi, il primo e' di sicurezza, in quanto un errato o un prematuro comando Write potrebbe cancellare in maniera irreparabile i dati gia' memorizzati, mentre il secondo e' di velocita', poiche' ogni operazione di scrittura richiede l' esecuzione di diverse procedure, condotte in maniera trasparente, ma che se ripetute portano ad un eccessivo rallentamento, ne citero solo alcune : accensione del motore del drive, raggiungimento del punto desiderato, conversione dei dati nel formato MFM o GCR (a seconda della formattazione adottata), scrittura in DMA sul supporto magnetico, quindi, grazie alla scelta effettuata dalla Commodore, possiamo alterare piu' volte il buffer e solo quando i nostri dati verrebbero persi essi saranno memorizzati permanentemente sul disco.
CLEAR
Qualcuno potrebbe, a questo punto, avanzare la seguente domanda : se mi accorgo che i dati che voglio registrare sono ormai corrotti, come posso impedire la loro memorizzazione, visto che ogni operazione distruttiva del buffer porta ad un' automatica memorizzazione degli stessi su disco ?
La risposta e' semplice : usando il comando CLEAR, che piuttosto che ripulire il buffer (come il suo nome fa intuire), si limita in maniera rapida ad invelidarne il contenuto, e cioe' altera la sua checksum, impedendo il suo trasferimento su disco prima della successiva Read o Update.
SEEK
Questo comado serve per posizionare la testina di lettura/scrittura nel punto desiderato, questa operazione viene eseguita, senza la sua richiesta esplicita, dai comandi Read, Update ...
MOTOR
Serve per variare lo stato del motore del drive da ON ad OFF o viceversa.
In un sistema multitasking e' difficile prevedere se due o piu' taskes accederanno contemporaneamente allo stesso drive, ed in tale circostanza potrebbe nascere un conflitto, faccio un esempio. Supponiamo di avere due taskes, che indico con TASK1 e TASK2, supponiamo che il TASK1, terminato il suo accesso, spegna il drive, a questo punto il TASK2 si trovera' con il drive spento, e dovra riaccenderlo perdendo tempo prezioso, poiche' ci voglio alcuni giri prima che il Floppy Disk raggiunga la velocita' corretta di 300 RPM, e' probalile che questa situzione si ripresenti piu' volte, rallentando notevolmente l' esecuzione dei due Taskes, quindi e' buona regola non spegnere il drive se non ne abbiamo piu' bisogno.
FORMAT
Come il nome stesso indica questo comando esegue la formattazione del disco. Contrariamente a quanto si possa pensare e' ingrado di scrivere gia' nelle tracce che formatta rendendo superflua la successiva operazione WRITE sulle stesse, quindi potremmo usarlo per crearci un copiatore ultrarapido (non aspettatevi miracoli!) su misura. Supponiamo di avere sufficiente memoria per memorizzare un intero disco in memoria, chi possiede 1 mega o piu' di Chip Ram, puo' farlo senza trucchi di buffer in Fast Ram, a questo punto non ci resta che inserire un dischetto, anche vergine, nel drive e dare alla trackdisk il comando format, otterremo cosi' una copia perfetta con un solo comando. Anche il Format come il Write deve essere seguito da Update per divenire operativo.
Penso di aver detto abbastanza, ora tocca a voi provare e riprovare per approfondire le vostre conoscenze, e non lasciatevi scoraggiare dai primi insuccessi.
Per maggiori informazioni pratiche vi rimando al sorgente Assembly 68000 da compilare con il MasterSEKA, ma può essere facilmente riadattato anche ad altri ambienti come il Devpac.
-------------------------------------------------------------------
; Struttura della Message Port
00E MP_FLAGS ds.b 1 ; Flag
00F MP_SIGBIT ds.b 1 ; Numero bit segnale
010 MP_SIGTASK ds.l 1 ; ^Task per il Sig
014 MP_MSGLIST ds.b LH_SIZE ; Lista dei Message
MP_SIZE equ $022
(LH_SIZE equ $00E ; From LISTS)
-------------------------------------------------------------------
Fig. 1
------------------------------------------------------------------
; Struttura di Request I/O
014 IO_DEVICE ds.l 1 ;^Struttura di Device
018 IO_UNIT ds.l 1 ;^unita'
01C IO_COMMAND ds.w 1 ;^comando del Device
01E IO_FLAGS ds.b 1 ;Flag
01F IO_ERROR ds.b 1 ; oppure codice segna.
IO_SIZE equ $020
; Estensione di I/O
020 IO_ACTUAL ds.l 1 ;Byte trasferiti
024 IO_LENGTH ds.l 1 ;totale Byte
028 IO_DATA ds.l 1 ;^Dati
02C IO_OFFSET ds.l 1 ;Offset se ricerca
IOSTD_SIZE equ $030
-------------------------------------------------------------------
Fig. 2
-------------------------------------------------------------------
; Estensione blocco Request I/O ( Trackdisk )
030 IOTD_COUNT ds.l 1
034 IOTD_SECLABEL ds.l 1
IOTD_SIZE equ $038
--------------------------------------------------------------------
Fig. 3
----------------------------------------------
; Comandi trackdisk
TD_MOTOR equ 9
TD_SEEK equ 10
TD_FORMAT equ 11
TD_REMOVE equ 12
TD_CHANGENUM equ 13
TD_CHANGESTATE equ 14
TD_PROTSTATUS equ 15
TD_LASTCOM equ 15
ETD_WRITE equ 3
ETD_READ equ 2
ETD_MOTOR equ 9
ETD_SEEK equ 10
ETD_FORMAT equ 11
ETD_UPDATE equ 4
ETD_CLEAR equ 5
N.B. Il comandi contrassegnati don ETD_
richiedono la presenza dell' estensione
della Fig. 3
----------------------------------------------
Fig. 4
------------------------------------------
; Error Codes ( in IO_ACTUAL )
TDERR_NOTSPECIFIED equ 20
TDERR_NO SECHDR equ 21
TDERR_BADSECPREAMBLE equ 22
TDERR_BADSECID equ 23
TDERR_BADHDRSUM equ 24
TDERR_BADSECSUM equ 25
TDERR_TOOFEWSECS equ 26
TDERR_BADSECHDR equ 27
TDERR_WRITEPROT equ 28
TDERR_DISKCHANGED equ 29
TDERR_SEEKERROR equ 30
TDERR_NOMEM equ 31
TDERR_BADUNITNUM equ 32
TDERR_BADDRIVETYPE equ 33
TDERR_DRIVEINUSE equ 34
-----------------------------------------
Fig. 5