Lichtspiele - Patentes zur Programmierung, Teil III

Was bisher geschah: Dr. Borg hätte es wissen müssen; das Projekt Cee-Dee ließ ihn nicht mehr los. Durch ungezählte Dokumentationen hatte er sich gewühlt, tausendmal das Wörterbuch gewälzt, manche Träne im Kopfkissen zerdrückt. Doch nun wußte er alles über die geheimnisvollen Vorgänge im Kloster Laserheim, er kannte die Rolle des unheimlichen Mister Frame, und sogar die Agenten Reed und Solomon hatte er durchschaut. Alles, was ihm fehlte, war seine Gefährtin Ada Rist, die ihm helfen sollte, der glitzernden Scheibe die Geheimdaten zu entreißen...

Ernst beiseite: Ich habe versucht. Ihnen zu zeigen, wie auf einer CD Daten aufgezeichnet werden und wie sie ein CD-ROM davon lesen kann. Sie sollten nun wissen, daß...

Der Plan für heute: Die ACSI-Kommandos des CDAR504 werden im Überblick vorgestellt, um Ihnen einen Eindruck von den Fähigkeiten der Kiste zu geben und um Programmierern zum Einstieg zu verhelfen. Schließlich hat das CDAR504 gute Software noch bitter nötig, und je schneller die Programmierer unter Ihnen Bescheid wissen, desto eher dürfen auch die Anwender auf gute Pakete hoffen. “ACSI? Walter?” Auch über den ACSI-Bus erfahren Sie in dieser Folge einige Grundbegriffe, mit denen Sie Ihren Bäcker beeindrucken können. Und last, but certainly not least (‘tschuldigung, aber mir fällt einfach keine gute deutsche Übersetzung dazu ein) habe ich Ihnen bereits eine komplette Sammlung von Assemblerroutinen zur Einbindung in eigene Programme zu bieten, so daß Sie sich mit den widerlichsten DMA-Interna überhaupt nicht herumschlagen müssen. Ist das nix? Wohlan:

Einsames CD-ROM sucht Anschluß

Das CDAR504 von ATARI hängt am DMA-Port des ST. Auf diesem DMA-Bus hat ATARI ein eigenes Protokoll zur schnellen Datenübertragung definiert: Das ACSI (ATARI Computer System Interface). Es hat einiges vom großen Bruder SCSI (sprich: “skassi”; steht für Small Computers System Interface) geerbt, aber leider nicht alles. Wenn Sie die Unterschiede interessieren, schlagen Sie doch mal bei [ 1 ] oder [2] nach. Für unsere Zwecke genügt eine Grundausbildung in Sachen ACSI. Anschnallen und das Rauchen einstellen.

Ganz schön fix - Der DMA-Port des ST

Am DMA-Port des ST, das wissen Sie, hängen Festplatten, Laserdrucker und jetzt auch das CD-ROM. Zur friedlichen Koexistenz braucht man ein Protokoll, an das sich alle halten. Wenn nur ein Quertreiber am Bus Faxen macht, läuft nichts mehr: Das sollte man zum Beispiel bedenken, wenn man sich eine der Billiglösungen für den Festplattenanschluß anschaffen möchte.

Im Prinzip bietet sich am DMA-Bus des ST, der ja eigentlich ACSI-Bus heißt, folgendes Bild (Bild 1):

Klare Verhältnisse am DMA-Bus

Der ST herrscht also über den Bus, an dem bis zu 8 verschiedene Zieleinheiten (Targets) angeschlossen sein können. Jede dieser Zieleinheiten besitzt einen Hostadapter, der die Signale des ACSI-Busses für den Controller umsetzt. An diesen Controller können jeweils bis zu 8 Geräte angeschlossen sein, je nachdem, wie leistungsfähig der Controller ausgelegt wurde (die Controller in den SH205-Platten von ATARI unterstützen zum Beispiel maximal 2 Plattenlaufwerke). Das heißt: Theoretisch könnte man bis zu 64 Peripheriegeräte per ACSI-Bus an den ST anschließen. Und das ist ja fürs erste schon ganz nett, oder?

Nun gibt es ja auch andere, ähnliche Buskonzepte, die ebenfalls den Anschluß vieler Peripheriegeräte erlauben; man denke nur an den IEC-Bus, der ja sogar (rudimentär) beim C64 implementiert war. Wie der ACSI-Bus setzt dieser - allerdings serielle -Bus intelligente Peripheriegeräte voraus, die sich zumindest ihre Schuhe selber binden können und den Rechner nicht mit ihrem eigenen Kram belästigen. Die Unterschiede: Der ACSI-Bus ist ein 8 Bit breiter paralleler Bus, und außerdem wird er von ATARIs DMA-Chip bedient.

Letzteres hat zur Folge, daß auf dem ACSI-Bus Datenübertragungsraten von bis zu 1.3 MByte/Sekunde möglich sind; die Übertragung selbst wird komplett vom DMA-Chip übernommen, der Prozessor bleibt frei.

Kochrezepte

Sie wissen also nun, was der ACSI-Bus kann, nur noch nicht, wie man’s macht. Um eine Übertragung anzuleiem, ist nämlich doch ein wenig Hackerei notwendig, bis man dem DMA-Chip und den beteiligten Controllern seine Wünsche klargemacht hat. Insbesondere ist wichtig, wie die Datenübertragung prinzipiell abläuft:

  1. Der ST wählt ein Zielgerät (“Target”) aus, indem ein bestimmtes Signal auf den Bus gegeben wird und danach das erste Byte eines Kommandoblocks, das neben der Kommandonummer auch die Targetnummer enthält.
  2. Über den Bus wird der Rest des Kommandos an das Zielgerät geschickt; ein Kommando umfaßt meistens 6, beim CDAR504 auch mal 10 Bytes.
  3. Je nach Kommando sendet dann
  1. Nach Beendigung des Kommandos schickt das Zielgerät ein Statusbyte an den Rechner, in dem auch gemeldet wird, ob ein Fehler aufgetreten ist.

Soweit das Prinzip. Wie das im einzelnen vor sich geht, ist eine lange Geschichte und für das folgende nicht dringend notwendig. Es ging mir nur darum, daß Sie mir nicht resigniert abwinken, wenn ich Sie gleich mit Targetnummern (Nummern der Zielgeräte), Kommandoblöcken und Statusbytes bombardiere. Was viel bedeutender ist als die wüste DMA-Hackerei: Was kann man mit dem CDAR504 machen? Welche Kommandos unterstützt es?

Die AUDIO-Kommandos

Damit die CDAR-Besitzer für die nächste Fete gerüstet sind, ziehe ich die Audio-Kommandos vor. Wer die Audio-Anwendung für Spielerei hält, möge bitte diesen Abschnitt überspringen. Wir anderen machen jetzt jedenfalls tüchtig Krach - das CDAR504 ist dafür bestens geeignet:

Audio Start (ACSI-Kommando 6) - Wiedergabe eines Songs starten

Byte    Bitcodierung
 1      T T T 0 0 1 1 0
 2      0 0 0 0 0 0 L R     L=0: linker Kanal an
                            R=0: rechter Kanal an
 3      0 0 0 0 0 0 0 0
 4      S S S S S S S S     Titelnummer
 5      F F F F F F F F     Anzahl der Titel
 6      C Q 0 0 0 0 0 0     “Continue”-/Schnelldurchlauf-Flag (Q)

Mit diesem Kommando kann man einzelne Titel auf einer Musik-CD anwählen und sie starten. Dabei kann man auch mehrere Titel hintereinander spielen lassen; dazu dient der Parameter im fünften Kommandobyte. (He! Kim Wilde groovt besser, als ich dachte...)

Wer’s einseitig liebt, kann einzelne Kanäle ausschalten; auch beide natürlich, wenn’s Ihnen Spaß macht. Ist das Schnelldurchlauf-Flag gesetzt, werden alle Songs nur jeweils 10 Sekunden angespielt. Im “Continue”-Bit wird angekündigt, daß dieses Kommando fortgesetzt wird und weitere Songnummern folgen. Das heißt also: Man schickt nach dem ersten AUDIO-START-Befehl mindestens einen weiteren nach. Löscht man das “Continue”-Flag im letzten AUDIO-START-Befehl, endet die Programmierung.

Audio Stop ($05) - Wiedergabe beenden

T T T 0 0 1 0 1     TTT = Targetnummer
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

Wahrscheinlich der Lieblingsbefehl meiner Mutter: Er schaltet die Audio-Wiedergabe aus - Funkstille.

Audio Program ($11) - Audio-Wiedergabe programmieren

T T T 1 0 0 0 1     TTT = Targetnummer 
0 0 0 0 0 0 L R     L=0: linker Kanal an,
                    R=0: rechter Kanal an 
M M M M M M M M     Startminute in BCD
S S S S S S S S     Startsekunde in BCD
F F F F F F F F     Startblock in BCD
M M M M M M M M     Endminute in BCD
S S S S S S S S     Endsekunde in BCD
F F F F F F F F     Endblock in BCD
0 0 0 0 0 0 0 0 
R 0 0 0 0 0 0 0     Repeat-Flag

Das ist das raffinierteste Audio-Kommando. Es erlaubt Ihnen, ganz kurze oder auch längere Teile aus einer Musik-CD herauszunehmen und sie wiederzugeben. Die kleinste Einheit ist dabei ein Block, also eine 75tel Sekunde. Das ist im Prinzip genug für allerlei Scratch-Effekte; allerdings sind die Positionierungszeiten beim CD-ROM eben leider zu groß, als daß man wirklich gute und flüssige Tricks damit hinbekommen könnte. Aber vielleicht überraschen uns ja noch pfiffige Programmierer. Das Werkzeug dazu, AUDIO PROGRAM, ist jedenfalls da. Ach ja, bevor ich’s vergesse: Setzt man das “Repeat”-Flag, wird der ausgewählte Ausschnitt unendlich oft wiederholt - solange, bis ein AUDIO-STOP-Kommando dem Ringelreih’ ein Ende setzt.

Wichtig übrigens bei den Zeitangaben in diesen Kommandos: Wie beim CD-ROM öfters üblich, werden die Zeiteff im BCD-Format codiert. BCD steht für Binary Coded Decimal; dabei wird ein Byte in zwei Nibbles (4 Bits) aufgeteilt, in denen Werte von 0 bis 9 stehen dürfen. Damit sind in einem Byte zwei Dezimalziffern darstellbar (also die Dezimalzahlen von 0 bis 99).

Beispiele:

0 0 0 1 0 1 0 0 = $14   Codierung für Dezimalzahl 14
1 0 0 1 0 1 1 1 = $97   Codierung für Dezimalzahl 97
0 0 0 0 1 0 1 0 = $0A   ungültige BCD-Codierung

Das alles ist ja schon ganz hübsch; aber wie kommt man überhaupt an die Information, welche Titel auf einer CD sind? Triviale Lösung: Auf dem Cover nachsehen. Hackerlösung: Inhaltsverzeichnis der CD einlesen. Inhaltsverzeichnis?

Read TOC ($19) - Inhaltsverzeichnis lesen

T T T 1 1 0 0 1         TTT = Targetnummer
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0         Das Rätsel-über-Rätsel-Flag

An dieser Stelle ein paar Bemerkungen über die Organisation einer CD: Vor dem eigentlichen Datenbereich, in einem Abschnitt, der etwa 150 Datensektoren entspricht, stehen mysteriöse Daten, an die man mit keinem Lese-Befehl herankommt (schnüff!). Halt - ich nehme fast alles zurück: Dort steht auch das erwähnte Inhaltsverzeichnis, auf neudeutsch auch TOC (Table of Contents) - und genau dieses liest der Befehl READ TOC ein. Im TOC wird Buch geführt über die Belegung der CD mit maximal 99 Titeln (Tracks).

Das Inhaltsverzeichnis besteht aus einzelnen Einträgen mit jeweils 4 Bytes:

Titelnummer Bedeutung
$00 Sie nannten ihn Nobody - so ein Eintrag hat keine Bedeutung.
$A0 Der Titel mit der Nummer, die im Minutenbyte steht, soll als erstes gespielt werden.
$A1 Der Titel mit der Nummer, die im Minutenbyte steht, soll als nächstes gespielt werden.
$A2 Minuten- und Sekundenfeld zeigen die Zeit an, bei der der letzte Titel endet.

Tabelle 1: Spezielle Titelnummern im Inhaltsverzeichnis

Alle Daten sind wieder BCD-codiert. Bei speziellen Titelnummern ändert sich die Bedeutung der restlichen Bytes (Tabelle 1). READ TOC liest einen 512 Byte langen Block mit 128 solchen TOC-Einträgen ein. Setzt man das Flag im letzten Byte, wird die Ordnung der Einträge kräftig durcheinandergeschüttelt; eine bestimmte Strategie konnte ich dabei aber noch nicht ersehen.

Sie sehen: Im Inhaltsverzeichnis steht nichts über die Titel und Interpreten; nur mickrige Startzeiten sind dort verzeichnet. Es fehlt auch eine eindeutige Kennzeichnung der eingelegten CD. Dem könnte man aber mit folgender Strategie abhelfen:

Man lese von allen Musik-CDs (oder auch Daten-CDs, denn die haben auch TOCs) das Inhaltsverzeichnis ein und bilde über die 512 Bytes eine hinreichend sichere Prüfsumme, beispielsweise mit einem CRC-Verfahren [2]. Dazu lasse man sich vom Anwender den Namen der eingelegten CD geben und speichere ihn zusammen mit der Prüfsumme ab. Wenn man nun später eine CD einlegt, liest man zuallererst wieder das Inhaltsverzeichnis, bildet die Prüfsumme und vergleicht sie mit den gespeicherten. Damit kann man dann auch den Titel der CD aus einer Liste heraussuchen. In dieser Liste könnten natürlich auch noch mehr Informationen über die CD stehen, etwa: “Heintje: Mama - vorher Arzt befragen”.

Ein weiteres nützliches Kommando:

**Test Unit Ready** ($00) - CDAR504 bereit?

T T T 0 0 0 0 0     TTT	= Targetnummer
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Bit Bedeutung
0 -
1 ERROR: Fehler aufgetreten
2 BUSY: CDAR504 arbeitet noch ein anderes Kommando ab
3 MEDIA: Platte gewechselt

Tabelle 2: CDAR504 meldet sich zum Rapport.

Wenn gerade noch ein Audio-Kommando abläuft, liefert TEST UNIT READY im Statusbyte ein gesetztes BUSY-Bit. Durch wiederholtes Abfragen kann man so das Ende eines Kommandos relativ genau abpassen. An dieser Stelle sind wohl endlich genauere Informationen über das Statusbyte angebracht. Zum Format siehe Tabelle 2.

Dieses Statusbyte wird natürlich nicht nur nach TEST UNIT READY, sondern nach jedem Kommando geliefert, so daß man vom Erfolg oder Mißerfolg seines Ansinnens rechtzeitig erfährt. Chronisch Neugierige werden das folgende Kommando lieben:

Request Sense ($03) - Statusinformationen holen

T T T 0 0 0 1 1     TTT = Targetnummer
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

REQUEST SENSE liefert einen Block von 32 Statusbytes (Tabelle 3).

Byte Inhalt/Bedeutung
0 $F0
1 $03
2 Fehlernummer:
0 = kein Fehler
1 = von der Hardware beseitigter Fehler
2 = Gerät nicht bereit
3 = Prüfsummenfehler
4 = Hardware-Fehler
5 = ungültiges Kommando
6 = keine Platte eingelegt
7 = falscher CD-Typ für das betreffende Kommando
3-6: 0
7 $18
8- 15: 0

Tabelle 3: Na, das sind ja Zustände - der Statusblock

Diesen Befehl verwendet man vor allem, wenn man im Statusbyte, das man auf ein Kommando hin bekommt, eine allgemeine Fehlerbedingung feststellt. Im Statusblock findet man genauere Informationen zur Fehlerursache. Bei meinem CD AR werden die Bytes 16-31 nicht korrekt gesendet; darum kann ich sie auch noch nicht richtig dokumentieren - ich bitte um Nachsicht. Geplant ist, daß dort Informationen über die aktuelle Position in einem Musikstück und auf der CD gemeldet werden - was ja recht praktisch sein kann, denn mit dieser Information kann man die fehlende Spielzeitanzeige im Display ersetzen.

Die Daten-Kommandos

Read ($08) - logischen Block lesen

T T T 0 1 0 0 0     TTT = Targetnummer 
0 0 0 H H H H H     Blocknummer, MSB 
M M M M M M M M     Blocknummer 
L L L L L L L L     Blocknummer, LSB
C C C C C C C C     Blockanzahl (1-255)
0 0 0 0 0 0 0 0

Dieses wichtigste Daten-Kommando liest eine Anzahl von logischen CD-ROM-Blöcken in den Rechner ein. Die Größe dieser logischen Blöcke stellt man mit MODE SELECT ein - siehe dort. Wichtig: Der erste lesbare Sektor hat die Nummer 150. Warum das so ist, können Sie sich denken, wenn Sie meine entsprechende Bemerkung beim READ-TOC-Befehl rekapitulieren.

Extended Read ($18) - Erweiterte Lesefunktion

T T T 1	1 0 0 0     TTT = Targetnummer
0 0 0 0 0 0 0 0
B B B B B B B B     Blocknummer (0) oder Minuten in BCD 
B B B B B B B B     Blocknummer MSB oder Sekunden in BCD
B B B B B B B B     Blocknummer 
B B B B B B B B     Blocknummer LSB oder 0
0 0 0 0 0 0 0 0 
C C C C C C C C     Blockanzahl, MSB
C C C C C C C C     Blockanzahl, LSB
0 A 0 0 0 0 0 0     Audio-Bit

Bei diesem Lese-Kommando kann die Blocknummer 24 Bit breit sein. Ist das Audio-Bit gesetzt, wird die Blockadresse in Minuten, Sekunden und Blocks aufgeschlüsselt (BCD!).

Mode Select ($15) - Datenmodus setzen

T T T 1 0 1 0 1     TTT = Targetnummer
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
L L L L P P P P     Modus (LLLL=logischeBlockgröße, 
                    PPPP=physikalische Blockgröße)
0 0 0 0 0 0 0 0

MODE SELECT stellt das CDAR504 auf die aktuellen logischen und physikalischen Sektorgrößen ein. Zur physikalischen Sektorgröße (liegt zwischen 2048 und 2336) siehe [3]; das CDAR504 kann aber darüberhinaus einen physikalischen Block in mehrere kleine logische Blöcke unterteilen. Schauen wir uns das Modusbyte und dessen Belegung an. Im oberen Nibble (Einheit von 4 Bits) stellt man die logische, im unteren die physikalische Blockgröße ein (Tabelle 4).

unteres Nibble physikalische Sektorgröße
0 2052/2340 Bytes
1 2048 Bytes
2 2336 Bytes
3 2048 Bytes
4 2336 Bytes
oberes Nibble logische Blockgröße
0 512 Bytes
2 256 Bytes
3 1024 Bytes
4 2048 Bytes
5 2052 Bytes
6 2336 Bytes
7 2340 Bytes

Tabelle 4: Daten in allen Schuhgrößen und Farben der Saison

Die Lese-Kommandos behandeln grundsätzlich nur logische Blöcke! Der Standard-Modus ist übrigens $41 (physikalische und logische Sektorgröße 2048 Bytes).

Paraphernalien

Die weiteren, etwas unwichtigeren Kommandos will ich Ihnen nicht mehr en détail zumuten. Aber kurz erwähnen möchte ich sie noch:

MODE SENSE ($1A): liefert den aktuellen Modus (per MODE SELECT einstellbar) in einem 16 Byte langen Datenblock

INQUIRE ($12): Kennung vom CD-ROM holen

MEDIA REMOVAL ($1E): Plattenauswurf erlauben/verbieten

SPINDOWN TIMER ($13): Nachlaufzeit des Drehmotors setzen

SEEK ($0B): logischen Block anfahren

EXTENDED SEEK ($1B): logischen Block suchen (die gleiche Erweiterung für SEEK, wie sie EXTENDED READ für READ ist)

Ans Werk

Sie finden das alles ja ganz nett, aber haben partout keine Lust, sich eigene Routinen für die CD-ROM-Ansteuerung zu schreiben? Kann ich verstehen. Aber Sie müssen ja auch nicht selbst zum Assembler greifen. Erstens gibt es von ATARI zum CDAR504 eine XBIOS-Erweiterung, die die wichtigsten Kommandos unterstützt. Und zweitens habe ich mich schon für Sie mit der Programmierung abgekämpft. Das Ergebnis: Eine Sammlung von Assemblerroutinen, die von Hochsprachen aus aufrufbar ist. “Floppyspielereien”-Leser fühlen sich hier zurecht erinnert an LOCKSLEY, den Befreier des Lesekopfes (eine Sammlung von Assemblerroutinen, mit denen man alle Eigenschaften des Floppycontrollers ausnutzen kann). FIAT_LUX (Listing 1) ist das CD-Pendant dazu. Alle Informationen über die Ansteuerung dieser Routinen finden Sie im Kasten “Es werde Licht!”.

In der nächsten Folge der Lichtspiele - ja, ich bekenne: Ich muß dazu mein ach so durchdachtes Konzept etwas umwerfen - finden Sie dann ein kleines Monitorprogramm in GFA-BASIC, das die FIAT_LUX-Routinen verwendet, um alles aus dem CDAR504 herauszukitzeln.

Vermißtenmeldungen

So eindrucksvoll die Kommandos des CDAR504 auch sind - eines fehlt: Die Möglichkeit, Musik-Daten direkt in den Rechner zu laden, um sie dort weiterzuverarbeiten. Zumindest nach meiner derzeitigen Kenntnis gibt es keinen Weg, an die Edel-Samples auf der Glitzerscheibe heranzukommen; die Lese-Kommandos funktionieren bei Daten-CDs. Oder gibt es einen Trick? Ich glaube nicht, aber das Gerät ist noch neu und unerforscht - wer weiß...

Wenn man dann jedenfalls noch ein Kommando hätte, mit dem man Daten vom Rechner aus direkt auf die Audio-Wandler des CDAR geben könnte, wäre die Kiste wirklich eine Sensation: CD-ROM, CD-Player und 16-Bit-Soundsampler der Spitzenklasse in einem! Hört mich da jemand bei ATARI?

Es scheint auch so, als ob das CDAR504 keine CDs unterstützt, die sowohl Musik als auch Daten beherbergen (das gibt es nämlich!). Auch das ist eine Einschränkung, die ich nicht ganz verstehe. Es gibt jedenfalls CD-ROMs für den PC-Bereich, die solche Fähigkeiten haben.

Sei’s drum. Sie wissen jetzt, was der Controller in einem CD-ROM schon alles für Sie leistet: Sektoren lesen, Musikstücke abfahren, Umrechnungen von physikalischen Sektornummern in logische Blöcke und umgekehrt. Auch wenn Sie kein Programmierer sind, können Sie so doch schon immerhin abschätzen, ob mit dem CD-ROM Anwendungen möglich sind, die für Sie interessant werden könnten.

Hmmm....ich glaube. Sie haben geschummelt. Sie können doch unmöglich das alles gelesen haben - in so kurzer Zeit! Zur Strafe müssen Sie den Artikel jetzt nochmals durchackern:

Was bisher geschah: Dr. Borg hätte es wissen müssen; das Projekt Cee-Dee ließ ihn nicht mehr los. Durch ungezählte Dokumentationen hatte er sich — [NEIN! Das Heft ist schon voll genug; irgendwann ist Schluß! Die Red.]

CB

Literatur:

[1] Labude: "Die Festplatte". ST-Computer 1/88-6/88

[2] Brod/Stepper: "SCHEIBENKLEISTER", Eschborn 1988

[3] Brod: “Lichtspiele", Teil II. ST-Computer 11/88

[4] Schmal: CDAR504 Reference Manual, ATARI 1988

# Es werde Licht!

FIAT_LUX ist eine Sammlung von Assemblerroutinen, die Sie in Hochsprachen wie C oder BASIC einbinden können; diese Unterroutinen erledigen die etwas kniffligen Arbeiten, die es am DMA-Bus zu verrichten gilt, wenn man seinem CD-ROM mehr als ein paar apathische Fehlermeldungen entlocken will. Die Routinen besitzen einen einheitlichen Einsprungspunkt; will man ein bestimmtes Kommando loswerden, muß man es erst in einer “Kommandostruktur” beschreiben.

Dieser Einsprungspunkt liegt bei der Hauptroutine “cdinter". In BASIC sähe eine Einbindung etwa so aus:

Speicher=Gemdos(&H48,L;2000) ! genügend Speicher ! per MALLOC ! besorgen Bload “fiat_lux.prg”,Speicher ! assembliertes Pro- ! gramm einladen Cdinter=Speicher+28 ! Einsprungpunkt ist ! das erste Byte nach ! dem Programmheader

Void C:Cdinter(L:Buf%,L:Buf2%,L:Compnt) ! Aufruf mit drei Para- ! metern

Wohlgemerkt: Für solch eine Einbindung ist eine komplett assemblierte “Programm”-Datei nötig; damit man diese einfach so ohne Relokation einladen kann, werden alle Variablen im Listing PC-relativ adressiert (FIAT_LUX ist also voll relokatibel). Der Quelltext ist übrigens auf den MADMAC-Assem-bler zugeschnitten; für andere Assembler wird man eventuell die Kommentare generell mit Sternchen (*) einleiten müssen.

Für C sollte man dagegen das beim Assemblieren entstehende Objektfile mit dem dazugehörigen Anwenderprogramm passend zusammenlinken. Der Einsprungspunkt cdinter ist dazu als global deklariert (je nach Assembler wird man eventuell die “.globl"-Anweisung in “.xdef" übersetzen müssen). Der Aufruf im C-Programm:

void cdinter(bufbuf2,compnt); 
char *buf; 
char *buf2;
COMSTRUCT *compnt;

Wie Sie dem obigen Aufruf schon ansehen, braucht FIAT_LUX beim Aufruf drei Paiameter - und zwar (in dieser Reihenfolge) Zeiger...

... auf einen Sektorpuffer (beim Lesen von Sektoren): buf
... auf einen Reset vepuffer (beim Lesen von Statusdaten etc.): buf2
... auf eine Kommandostruktur: compnt

Bleibt noch die Kommandostruktur zu erläutern, auf die compnt deutet. In C-Notation sieht sie so aus:

typedef struct
{   char comblock[10];  /* Kommandoblock */
    long status;        /* Status-Langwort */ *
    long timeflag;      /* Timeout-Flag */
    long dmaend;        /* DMA-Endadresse */
} COMSIRUCI;

Für Programmierer, die sich mit C nicht besonders auskennen: Die Kommandostruktur ist ein Speicherblock von 22 Bytes Länge; der Aufbau ist in Tabelle 5 beschrieben.

Nach jedem Kommando setzt FIAT_LUX die Felder ‘status’, ‘timeflag' und ‘dmaend' in der Kommandostruktur - wie sich’s gehört. Diese Ausgaben werden deswegen in der folgenden Übersicht über die Ein- und Ausgabeparameter bei den einzelnen Kommandos des CDAR504 nicht mehr erwähnt:

REQUEST SENSE

IN : Kommandoblock in der Kommandostruktur Adresse eines mindestens 32 Bytes langen Reservepuffers in buf2

OUT : Statusblock (32 Bytes) im Reservepuffer

READ

IN : Kommandoblock in der Kommandostruktur-Adresse eines mindestens 4 Bytes langen Reservepuffers in buf2; im ersten Langwort des Reservepuffers muß die logische Blockgröße stehen (also meistens 2048) Adresse eines genügend großen Sektorpuffers in buf

OUT : Sektorinhalte im Sektorpuffer

EXTENDED READ

IN : wie bei READ

OUT : ebenfalls wie bei READ

SEEK, EXTENDED SEEK, AUDIO PROGRAM, AUDIO START, AUDIO STOP, TEST UNIT READY, MEDIA REMOVAL, DISK SPINDOWN TIMER

IN : Kommandoblock in der Kommandostruktur

OUT : - (außer Statusbyte etc.)

READ TOC

IN : Kommandoblock in der Kommandostruktur Adresse eines mindestens 512 Bytes langen Puffers in buf

OUT : TOC im Puffer

MODE SENSE

IN : Kommandoblock in der KommandostrukturAdresse eines mindestens 16 Bytes langen Puffers in buf2

OUT : Modus-Block im Reservepuffer

INQUIRE

IN : wie bei MODE SENSE

OUT : CD-ROM-Kennung im Reservepuffer

Sollten Sie jetzt immernoch auf dem Schlauch stehen, muß ich Sie auf die nächste Folge der Lichtspiele vertrösten. Dort stelle ich einen CD-Monitor in GFA-BASIC vor, der die Anwendung von FIAT_LUX demonstriert. Diesmal hat einfach der Platz im Heft nicht mehr gereicht - FIAT_LUX (Listing 1) ist schon lang genug.

Größe in Bytes Name Bedeutung
10 comblock Hier wird das ACSI-Kommando übergeben, das meistens 6 Bytes, beim CDAR504 aber auch manchmal 10 Bytes lang sein kann (siehe Hauptartikel); die Targetnummer muß vom aufrufenden Programm im ersten Kommandobyte gesetzt werden!
4 status Ein Langwort, in dem FIAT_LUX nach dem Aufruf einen Statuswert liefert. Im unteren Byte des oberen Wortes steht das Statusbyte, das vom Controller des CD-ROMs nach jedem Kommando versendet wird. Im untersten Byte steht der aktuelle DMA-Status: $00,$02 DMA-Fehler! $01 OK(evt. Daten nicht komplett übertragen) $03 Alles Roger Moore
4 timeflag Ein Lang wort, in dem FIAT_LUX signalisieren kann, daß ein Timeout aufgetreten ist; Timeout bedeutet: Das angesprochene Gerät hat nicht innerhalb einer vorgegebenen Zeitspanne auf die Anfrage reagiert - die Routine wurde daher erfolglos abgebrochen. Wenn solch ein Fall auftritt, wird timeflag auf -1 ($FFFFFFFF) gesetzt.
4 dmaend Ein weiteres Langwort, in dem FIAT_LUX am Ende eines Kommandos die aktuelle DM A-Adresse ablegt. Damit kann man beispielsweise kontrollieren, ob eine Übertragung auch komplett stattgefunden hat. Wollte man etwa einen Sektor von 2048 Bytes Länge übertragen und stellt dann fest, daß die Differenz zwischen dem Wert in diesem Langwort und der Startadresse (der Adresse des Puffers also, in dem die betreffenden Daten stehen) 512 ist, dann ist entweder der 1.April, oder wir haben da einen handfesten Fehler.

Tabelle 5: Aufbau der FIAT_LUX-Kommandostruktur


*****************************************
* FIAT_LUX.S                            *
* Das Software-Interface zum            *
* für die "ST-Computer"                 *
* V1.0, 16.11.88, 2 a.m.                *
* Written & (C) by Claus Brod           *
*                  Am Felsenkeller 2    *
*                  8772 Marktheidenfeld *
*****************************************

.globl  cdinter         ; global für Linker

*****************
* Systemvariablen und Konstanten 
*****************

dlow    = $ff860d       ; DMA-Adreßzähler,Lowbyte
dmid    = $ff860b       ; DMA-Adreßzähler,Midbyte
dhigh   = $ff8609       ; DMA-Adreßzähler,Highbyte
gpip    = $fffa01       ; GPIP-Register des MFP
daccess = $ff8604       ; DMA-Access-Register
dmodus  = $ff8606       ; DMA-Modus-Register
flock   = $43e          ; Floppy-VBL-Sperre
hz_200  = $4ba          ; 200-Hz-Zähler

STATUS  = 10            ; Offset des Status in der Kommandostruktur 
TIMEFLAG= 14            ; Offset des Timeout-Flags
DMAEND  = 18            ; Offset der DMA-Endadresse-Variablen

***************************
* cdinter: Einsprungspunkt
* Aktiviert Supervisormodus, analysiert
* Modus-Langwort und verteilt danach;
* terminiert nach Umschalten in Usermodus
* Auf dem Stack wird übergeben:
* 4(sp)   Zeiger auf Sektorpuffer
* 8(sp)   Zeiger auf Reservepuffer
* 12 (sp) Zeiger auf Kommandostruktur:
*         typedef struct {
*           char comblock[10];
*           long status;
*           long timeflag;
*           long dmaend;
*         } COMSTRUCT; 
****************************
cdinter:
    link a6,#0
    movem.l d0-a5,-(sp)     ; Register retten 
    move.l 16(a6),a1        ; Zeiger a.Kommandostruktur
    move.l 12(a6),a5        ; Zeiger auf Reservepuffer (buf2)
    move.l 8(a6),a4         ; Zeiger auf Sektorpuffer (buffer)

    clr.l -(sp)             ; in den Supervisormodus
    move.w #32,-(sp)        ; SUPER
    trap #1                 ; GEMDOS
    addq.l #6,sp
    move.l d0,-(sp)         ; alten SP retten

    clr.l TIMEFLAG(a1)      ; Timeout-Flag löschen
    st flock                ; Floppy-VBL sperren
    moveq.l #0,d0           ; d0.l löschen
    clr.w d3                ; 6-Byte-Kommandos
  * d3.w = 0: 6-Byte-Kommandos
  * d3.w = 1: 10-Byte-Kommandos

    move.l a1,a0            ; Zeiger auf Kommandoblock kopieren

********************
* Hier wird der Kommandoblock analysiert
* IN: D0.L = 0
*     A0.L ->Kommandoblock
*     SR   Supervisormodus
*     Floppy-VBL gesperrt
********************
    move.b (a0),d0          ; erstes Kommandobyte holen
    and.w #31,d0            ; nur die unteren 5 Bits interessieren

    cmp.b #3,d0             ; REQUEST SENSE?
    beq cdreqsense
    cmp.b #8,d0             ; READ?
    beq cdread
    cmp.b #$18,d0           ; EXTENDED READ?
    beq cdextread
    cmp.b #$1b,d0           ; EXTENDED SEEK?
    beq.s cdextseek
    cmp.b #$11,d0           ; AUDIO PROGRAM?
    beq.s cdaudprg
    cmp.b #$19,d0           ; READ TOC?
    beq.s cdrdtoc
    cmp.b #$1a,d0           ; MODE SENSE?
    beq.s cdmdsense 
    cmp.b #$12,d0           ; INQUIRE?
    beq.s cdinquire

* alles andere ist stdcommand 

*************************
* stdcommand: Schickt Standard-Kommandoblock
* IN: a0.l Zeiger auf comblock 
*************************
stdcommand:
    move.w #$8a,d1          ; DMA-Modus
    bsr sdltoend            ; Kommandoblock schicken

    move.w #$8a,dmodus      ; ACSI-Bus selektieren
    bsr getstatus           ; Status holen

************************
* exitus: Interface beendet die Arbeit 
************************
exitus:
    clr flock               ; Floppy-VBL freigeben

    move.w #32,-(sp)        ; SUPER
    trap #1                 ; GEMDOS
    addq.l #6,sp
    movem.l (sp)+,d0-a5     ; Register holen
    unlk a6
    rts                     ; raus hier

**********************
* cdextseek: EXTENDED SEEK für CD
* cdaudprg: AUDIO PROGRAM für CD 
**********************
cdextseek: 
cdaudprg:
    moveq.l #1,d3           ; 10-Byte-Kommando
    bra stdcommand

**********************
* cdrdtoc: READ TOC für CD 
**********************
cdrdtoc:
    moveq.l #1,d5           ; 1 Block übertragen
    move.l a4,d6            ; ->Puffer
    bra rdentry             ; und lesen

**********************
* cdmdsense: MODE SENSE für CD
* cdinquire: INQUIRE 
**********************
cdmdsense: 
cdinquire:
    moveq.l #1,d5           ; 1 Block übertragen
    move.l a5,d6            ; ->Reservepuffer
    bra rdentry             ; und los geht's

**********************
* calcblk: Errechnet die Anzahl der
* zu übertragenden DMA-Blocks (512 Bytes)
* IN: d0.w Blockanzahl (aus Kommando)
*     a5 Zeiger auf logische BlockgröPe in Bytes
*     (Langwort, steht am Anfang des Reservepuffers)
* OUT: d5.w Anzahl der DMA-Blocks
* USES: d6.1 
**********************
calcblk:
    move.l (a5),d5          ; Blockgröße holen (steht in buf2)
    mulu.w d0,d5            ; mal Blockanzahl

    divu   #512,d5          ; in DMA-Blocks umrechnen
    move.l d5,d6            ; Ergebnis nach d6
    swap   d6               ; Worthälften tauschen
    tst.w  d6               ; Division mit Rest?
    beq.s  calc1            ; nein
    addq.w #1,d5            ; ein Block mehr
calc1: 
    rts

**********************
* cdreqsense: REQUEST SENSE für CD
* IN: A0.L -> Kommandoblock 
**********************
cdreqsense:
    bsr rdtoggle            ;DMA-Status/Puffer löschen
    move.w #1,daccess       ;max. 1 DMA-Block übertragen

    move.l a5,d0            ; Startadresse des Statuspuffers 
    bsr setdma              ; in DMA-Zählerregister

rqlp:
    move.w #$8a,d1          ; Moduswort $8A
    clr.w d0                ; d0.w löschen
    bsr sdltox              ; die ersten x Kommandobytes
    bmi rwerr               ; Timeout?

    move.l #$0a,daccess     ; letztes Kommandobyte, DMA starten 
    bsr waitforcom          ; Übertragung abwarten
    bmi rwerr               ; Timeout?

    move.w #$8a,dmodus      ; Bus selektieren
    nop
    move.w daccess,d0       ; CS low, ACK bleibt High,R/W auf R

    moveq.l #130,d2         ; 100 reicht auch
as_time_goes_by:
    dbf d2,as_time_goes_by  ; Zeitschleife

    move.w #$8a,dmodus      ; Bus selektieren

    bsr getst2              ; Status holen
    bra exitus

**********************
* cdextread: EXTENDED READ für CD 
**********************
cdextread:
    moveq.l #0,d0
    move.b 7(a0),d0         ; MSB der Blockanzahl
    lsl.w #8,d0             ; mal 256
    move.b 8(a0),d0         ; LSB der Blockanzahl
    moveq.l #1,d3           ; 10-Byte-Kommando
    bra.s cdrl              ; weiter wie bei READ

**********************
* cdread: READ für CD
* IN: A0.L -> Kommandoblock 
********************** 
cdread:
    moveq.l #0,d0
    move.b 4(a0),d0         ; Blockanzahl holen
cdrl:
    bsr calcblk             ; Anzahl der DMA-Blocks ausrechnen 
    move.l a4,d6            ; -> Puffer

rdentry:
    move.w #$88,dmodus      ; Busanforderung
    moveq.l #0,d0           ; d0 löschen
    move.b (a0),d0          ; Kommando und Target holen
    swap d0                 ; Worthälften vertauschen
    move.w #$8a,d0          ; Modus einkopieren
    move.l d0,daccess       ; und alles auf den Bus
    bsr zeiteisen           ; auf Bestätigung warten
    bmi.s rwerr             ; Timeout?

    move.l d6,d0            ; Pufferadresse
    bsr setdma              ; als DMA-Startadresse
    move.w #$8a,d1          ; DMA-Modus
    bsr.s sd2tox            ; Kommandobytes 2 bis x übergeben 
    bmi.s rwerr             ; Fehler?

    bsr rdtoggle 
rdcnt:
    move.w d5,daccess       ; Sektoranzahl an DMA-Sektorzähler
    nop
    move.w #$8a,dmodus      ; HDC wieder selektieren 
    nop
    move.l #$a,daccess      ; Übertragung starten 
    bsr waitforcom          ; auf Kommandoende warten

    move.w #$8a,dmodus      ; HDC selektieren
    nop

rwerr:
    bsr getstatus           ; Status holen
    bra exitus              ; und raus

**********************
* sd2tox: Schickt die Kommandobytes 2 bis x
* auf den ACSI-Bus (per sdbyte)
* IN: a0.l Zeiger auf comblock
*     d1.w DMA-Modus
*     d3.w = 0: 6-Byte-Kommando (x=5)
*          = 1: 10-Byte-Kommando (x=9)
* OUT: N-Flag gesetzt, wenn Timeout
********************** 
sd2tox:
    movem.l d0/d2/d4,-(sp)  ; d0,d2,d4 retten
    moveq.l #0,d0           ; d0.l löschen
    clr.w d2                ; d2.w initialisieren
    moveq.l #4,d4
    tst.w d3                ; 10-Byte-Kommando?
    beq.s sdloop 
    moveq.l #8,d4

sdloop:
    addq.w #1,d2            ; d2++
    move.b 0(a0,d2),d0      ; Kommandobyte holen
    bsr.s sdbyte            ; und auf Bus legen
    bmi.s sd2err            ; Timeout?

    cmp.w d4,d2             ; schon x-1 Bytes übertragen? 
    bne sdloop              ; nein, weitermachen

sd2err:
    movem.l (sp)+,d0/d2/d4  ; Register zurückholen
    rts                     ; und raus

**********************
* sdltox: Schickt Kommandobytes 1 bis x a. ACSI-Bus
* (per sdbyte)
* IN: a0.l Zeiger auf comblock
*     d1.w DMA-Modus
*     d3 = 0: 6-Byte-Kommando
*     d3 = 1: 10-Byte-Kommando
* OUT: N-Flag gesetzt, wenn Timeout 
***********************
sdltox:
    clr.w d0                ; d0.w löschen
    move.w #$88,dmodus      ; ACSI-Bus wachrütteln
    move.b (a0),d0          ; erstes Kommandobyte holen
    bsr.s sdbyte            ; und auf ACSI-Bus legen
    bmi.s sdlerr            ; Timeout?
    bsr sd2tox              ; Kommandobytes 2 bis x schicken
sdlerr: 
    rts

***********************
* sdltoend: Schickt Kommandobytes 1 bis 6 bzw. 10 auf ACSI-Bus
* (per sdbyte)
* IN: a0.l Zeiger auf comblock
*     d1.w DMA-Modus
*     d3 = 0: 6-Byte-Kommando
*     d3 = 1: 10-Byte-Kommando
* OUT: N-Flag für Timeout 
*********************** 
sdltoend:
    bsr sdltox              ; Kommandobytes 1 bis x schicken
    bmi.s sderr             ; Timeout?
    move.b 5(a0),d0         ; letztes Kommandobyte holen
    tst.w d3                ; 10-Byte-Kommando?
    beq.s sdlast            ; nein, weiter
    move.b 9(a0),d0         ; letztes Kommandobyte holen
sdlast:
    swap d0                 ; Worthälften vertauschen
    move.w d1,d0            ; DMA-Modus
    move.l d0,daccess       ; auf Bus damit 
    bsr.s waitforcom        ; auf Kommandoende warten 
sderr: 
    rts

**********************
* sdbyte: Byte in d0 an HDC schicken
* IN:   d0.l wie folgt:
*       ------------
*       | X.b | X.b | 0.b | CB.b|
*       ------------
*       CB=Commandbyte
*       d1.w DMA-Modus ($8A/$18A) 
***********************
sdbyte:
    swap d0                 ; Worthälften vertauschen
    move.w d1,d0            ; DMA-Modus einschieben
    move.l d0,daccess       ; Byte auf Bus und Modus an DMA-Chip 
                            ; drop-thru...

*********************
* zeiteisen: Wartet auf IRQ des HDC
* USES: d0,d1 
********************* 
zeiteisen:
    movem.l d0/d1,-(sp)

    moveq.l #0,d0           ; Fehler-Default
    moveq.l #20,d1          ; 20 Ticks warten
gettimer:
    add.l hz_200,d1         ; d1 plus 200Hz-Zähler
zeita:
    btst #5,gpip            ; auf HDC-IRQ testen
    beq.s fix_und_fertig    ; ist angekommen, fertig
    cmp. 1 hz__200,d1       ; Timer-Zielwert erreicht?
    bne zeita               ; nein, weiter

    moveq #-1,d0            ; Fehler übermitteln
    move.l d0,TIMEFLAG(a1)  ; Timeout-Flag setzen
fix_und_fertig:
    tst.l d0                ; N-Flag aktualisieren
    movem.l (sp)+,d0/d1
    rts                     ; und raus

**********************
* waitforcom: Wartet nach Kommandoblock auf Kommandoende
* USES: d0,d1,a2 
********************** 
waitforcom:
    movem.l d0/d1,-(sp)
    moveq.l #0,d0           ; Default-Fehler
    move.l #800,d1          ; langes Timeout
    bra.s gettimer          ; und warten

********************
* setdma: DMA-Startadresse setzen
* IN: d0.L Pufferadresse 
******************** 
setdma:
    move.b d0,dlow          ; Lowbyte
    asr.l #8,d0
    move.b d0,dmid          ; Midbyte
    asr.l #8,d0
    move.b d0,dhigh         ; Highbyte
    rts

********************
* getdma: DMA-Adresse lesen und in dmaend
* ablegen 
******************** 
getdma:
    clr.w d0
    move.b dhigh,d0         ; Highbyte lesen
    asl.l #8,d0             ; mal 256
    move.b dmid,d0          ; Midbyte
    asl.l #8,d0             ; mal 256
    move.b dlow,d0          ; Lowbyte
    move.l d0,DMAEND(a1)    ; DMA-Adresse ablegen
    rts                     ; und raus

*********************
* rdtoggle: DMA-Puffer und DMA-Status löschen
* Sektorzähler selektieren, Datenrichtung auf lesen
********************* 
rdtoggle:
    move.w #$98,dmodus 
    nop
    move.w #$198,dmodus 
    nop
    move.w #$98,dmodus 
    rts

**************************
* getstatus: Liest Statusbyte
* deselektiert HDC
* ACSI-Bus muß bereits selektiert sein ($8a oder $18a nach dmodus)
* OUT: Status in status
* USES: d0,a2 
************************** 
getstatus:
    move.w daccess,d0       ; Status holen

getst2:
    swap d0                 ; Worthälften tauschen
    move.w dmodus,d0        ; DMA-Status holen
    and.l #$ff0007,d0       ; ausmaskieren
    move.l d0,STATUS(a1)    ; in status ablegen

    move.w #$80,dmodus      ; FDC selektieren
    bra getdma              ; DMA-Adresse holen

Listing: FIAT_LUX



Aus: ST-Computer 01 / 1989, Seite 52

Links

Copyright-Bestimmungen: siehe Über diese Seite