Vor Monaten angekündigt, lange erwartet und seit einiger Zeit endlich zu haben: Der Blitter! Der Blitter, dieser sagenumwobende Chip, ist ein Käfer mit vielen Beinen und übernimmt in den neuen ATARIs die Funktion ein Bildauschnittkopierers. Die Kopierroutine BITBLT wurde 1979 zum erstenmal ausführlich beschrieben. ATARI liefert nun mit ihrem Chip eine Hardwarelösung dieser BITBLT-Routine. Wir wollen in diesem Monat nicht die Programmierung (dies folgt später einmal), sondern das Beheben der seltsamen Abstürze des Blitters besprechen.
Seit einiger Zeit häufen sich nun immer mehr Gerüchte, daß am Blitter irgend etwas nicht stimmt, denn es zeigt sich, daß in STs, die einen Blitter benutzen und Grafikbefehle ausführen, manches nicht mit rechten (oder linken) Dingen zugeht - der Rechner stürzt aus unerfindlichen Gründen ab. Glücklicherweise gibt es Leute, die nichts besseres im Kopf haben, als solchen Dingen auf den Grund zu gehen, zumal der Verlust eines in mühevoller Kleinarbeit erarbeiteten und nicht abgespeicherten Bildes wegen eines Rechnerabsturzes einige Zeit (und Nerven) kostet. Außerdem hat 1st-Word-Plus die unangenehme Eigenschaft, bei Benutzung des Blitters und Kursivschrift öfters einmal abzustürzen. Schon nach kurzer Recherchenzeit war klar, daß der Fehler nur mit dem Blitter zu tun haben konnte. Ob an dem Gerücht etwas dran ist, daß manche Blitter einen Maskenfehler haben, ist schwer zu sagen: ATARI verneint es und auf der CeBit waren die Abstürze angeblich unbekannt. Sei es drum: Dann wollen wir uns also mit Abstürzen beschäftigen, die es nicht gibt !
Um es vorwegzunehmen: Die heutige ST-Ecke ist nicht für Anfänger gedacht, trotzdem sind auch diese eingeladen, weiterzulesen. Ich versuche das Folgende möglichst einfach zu erklären, allerdings kommt man um Assemblerkenntnisse nicht herum. Zunächst möchte ich kurz auf die Ausnahmeverarbeitung (Exception) des 68000er eingehen. Wie wahrscheinlich hinreichend bekannt, gibt es einige Situationen bei diesem Prozessor (wie natürlich auch bei anderen), bei denen er nicht mit dem einverstanden ist, was der Programmierer ihm vorgibt. Dies kann zum Beispiel eine Division durch Null, einen Wortzugriff auf eine ungerade Adresse oder ein Zugriff auf eine nicht freigegebene Adresse sein. Bei dem letzteren handelt es sich um einen sogenannten Busfehler, der auch beim Blitterfehler eintritt. Nähere Nachforschungen ergaben nun folgendes: Der 68000 kann, während er einen Befehl bearbeitet (wie zum Beispiel move.x Register, Register), bei dem kein Buszugriff erfolgen muß, schon den nächsten Befehl aus dem Speicher einlesen, was sicherlich für die Eingeweihten unter Ihnen keine Neuigkeit ist. Diesen Vorgang nennt man Prefetch, was frei übersetzt soviel wie ‘vorzeitiges Holen' bedeutet. Wer sich schon einmal die von ATARI angegebenen Informationen zur Blitterprogrammierung angeschaut hat, wird erkennen, daß man den Blitter durch das Setzen eines Bits einschaltet, gefolgt von einem NOP. “Ein NOP ?”, wird der aufmerksame Leser fragen. Die Antwort habe ich schon indirekt gegeben. Der Prozessor holt während der Ausführung des BSET-Befehls schon den nächsten Befehl (unseren NOP). Sobald aber der Blitter sein Bit zum Einschalten erkennt, übernimmt er den Bus, was bedeutet, daß unser NOP-Befehl nicht mehr zur Ausführung kommt - er ist praktisch ein Füllbefehl.
Beim Blitterfehler kann man allerdings nicht genau sagen, in welchem Buszyklus der Busfehler eintritt. Nach einigem Überlegen kamen wir (hallo Jörg und Alex!) auf die Idee, daß es sich hier um irgendwelche Zeitprobleme in der Rechnerstruktur handelt, die eventuell auf Leitungsreflexionen oder falsche Zugriffsprotokolle bei bestimmten Vorgängen im Blitter zustande kommen. Da wir aber das Problem momentan nicht mit Zange, Schraubendreher und Lötkolben sondern mit unserer bescheidenen Intelligenz softwaremäßig lösen wollen, haben wir das Oszilloskop in der Ecke stehen lassen und den Assembler ausgepackt.
Aus Stunden der Überlegungen und Nachforschungen ist nun GOOD-BLIT hervorgegangen. Dieses Programm fängt einen Busfehler ab, prüft, ob es sich um einen Blitterfehler handelt und startet den Blitter erneut. Dies versucht es etwa 10 Sekunden lang. Sollte dabei immer wieder festgestellt werden, daß der Neustart erfolglos ist oder daß es sich nicht um einen Blitterfehler handelt, so erfolgt ein Einsprung in die normale Exception-Behandlung des ATARI. Der Haken an dat janzem ist der Satz “ob es sich um ein Blitterfehler handelt”, denn Busfehler können ja nicht nur vom Blitter aus verursacht werden. Man muß also sorgen, daß das Fehlerfenster recht klein ist, so daß möglichst nur Blitterfehler abgefangen werden. Die einzige Aussage, die man machen kann, ist, daß der Prozessor sich bei einem Blitterbe-fehl im Supervisor-Modus befindet und einen READ-Zyklus ausgeführt hat. Diese Information kann man dem Statuswort entnehmen, daß der Prozessor beim Busfehler auf den Stack legt.
Bei einem Busfehler legt die CPU einige Informationen auf den Stack, die es dem Programmierer ermöglichen, herauszufinden, wo, (vielleicht) weshalb und warum sich der Busfehler ereignete. Eine genauere Beschreibung der Exceptions und deren CPU-seitige Betreuung kann man in der einschlägigen Literatur nachschlagen. Wir wollen hier nur kurz drei Information andeuten, die auf dem Stack liegen.
Da wäre zum einen die PC- (Program-Counter=Programmzähler) Adresse, in der der Befehl steht, bei dem ein Busfehler aufgetreten ist. Dieses Langwort liegt 10 Bytes entfernt von der Adresse des Stackpointers (SP). Zwei Bytes von SP entfernt liegt die Fetch-Adresse und on Top (auf deutsch: ganz oben) befindet sich ein ganz besonderer Leckerbissen. In diesem Statuswort steht, ob der Prozessor beim Busfehler im Supervisor war, ob es sich um einen Lese- oder Schreib-Zyklus handelte und (!) ob der Prozessor im Instruction-fetch war, als das Unvermeidliche passierte. Passierte ein Blitterfehler, wissen wir, daß der Prozessor im Lese-Zyklus und im Supervisor-Modus war. Diese Zusammensetzung ergibt für das Statuswort den Wert $16 (die Bits des Statusregister hier einzeln zu beschreiben, würde den Rahmen sprengen). Die Information im Statuswort, ob der Prozessor im Instruction-fetch war, können wir leider nicht verwerten, da es theoretisch möglich sein kann, daß der Busfehler nach Ausführen des ersten Fehlers stattfinden kann. Um die Möglichkeit noch zusätzlich einzukreisen, werden in GOODBLIT noch folgende Dinge abgetestet.
a) Fetch-Adresse größer als PC?
b) Abstand zwischen PC und Fet-chadresse <10?
c) Ist in einem der Adressregister eine Blitteradresse vorhanden?
d) Ist in dem Befehl, der zum Auslösen des Busfehlers beitrug, eine Blitteradresse vorhanden ?
Falls a und b mit JA und c oder d mit JA beantwortet werden kann, kann man mit großer Sicherheit davon ausgehen, daß unser Blitter der Übeltäter war. Am Rande soll bemerkt werden, daß GOODBLIT einen sehr hohen Prozentsatz des Blitterfehlers abfängt, aber hexen kann es auch nicht. Es sei also immer Vorsicht geboten. Fazit: Lieber einmal zuviel als zuwenig abgespeichert (Eine Eigenschaft, die man sich sowieso aneignen sollte, ob man nun Programmierer oder Anwender ist!!!).
Goodblit wurde mit dem Metacomco-Macro-Assembler entwickelt. Anpassungen an andere Assembler dürften für den Assembler-Programmierer aber kein Problem sein. Zu Anfang wird das Programm speicherresident initialisiert. Den genauen Vorgang und welche Tricks es dabei gibt, werde ich der nächsten ST-Ecke beschreiben, auch wenn dieser Teil in diesem Monat schon vollständig abgedruckt ist.
Nun geht’s ans Eingemachte: Zunächst werden alle Möglichkeiten eines Interrupts verhindert und ein paar Register zwischengespeichert, die wir verwenden werden. In Check# 1 prüfen wir, ob es sich um einen Busfehler handelt, dessen Statuswort, das wie oben erwähnt am Anfang des Stacks liegt, das Bitmuster lxllx (sie erinnern sich $16) enthält. Falls nicht, folgt die normale Busverarbeitung... Die zweite Prüfung, die der Busfehler zu bestehen hat: Ist die Programmadresse - 10(SP) - größer als die Prefetchadresse -2(SP)? Wir haben auf empirische Weise ermittelt, daß das bei einem Blitterfehler nicht der Fall ist. Außerdem war auch der Abstand zwischen PC- und Prefetch-Adresse kleiner 10, was der Länge des längsten Befehls entspricht. Der Vorteil ist, daß wir dadurch auch sicher sein können, daß die Prefetch-Adresse die Adresse unseres nächsten Befehls ist, den wir zur Fortsetzung des Programms benutzen (siehe unten).
Da die beiden folgenden Abfragen, ob in einem Register oder in einem Befehl eine Blitteradresse enthalten ist, etwas diffizil sind, gehe ich näher darauf ein.
Zunächst die Registerabfrage. Das Interessante an dieser Routine ist, daß die Inhalte der Adressregister in einer Schleife abgefragt werden und nicht nacheinander wie zum Beispiel: “Beinhaltet Register AO einen Blitter-wert, beinhaltet Register Al einen Blitterwert” (sie werden wissen, wie es weitergeht). Die Schleifenlösung ist, um es vorwegzunehmen, in einem ROM nicht möglich, da der Befehl modifiziert wird! Betrachtet man sich den move-Befehl, so erkennt man, daß die Bits 0 bis 2 des zweiten Befehlsbyte die Registernummer in binärkodierter Form enthält. Schreiben wir also durch unsere Schleifenkonstruktion nacheinander die Werte 0 bis 7 in den Befehl, liest der Befehl, abhängig vom Schleifenzähler die Register AO bis A7 ein. Anschließend erfolgt einfach die Überprüfung, ob die Adresse des Registers im Bereich von FF8A00 bis FF8A3F liegt. Check#4 testet, ob in dem Befehl(!), der zum Busfehler führte, eine Adresse des Blitters zu finden ist. Dazu holt die Routine sich die Adresse dieses Befehls -10(sp)- und durchforstet ihn von Anfang bis Ende auf eine der möglichen Blitteradressen. Die Schleife wurde deshalb konstruiert, weil man nicht wissen kann, wo der Wert im Befehl steht.
Um ehrlich zu sein: Heilen kann man den Fehler nicht, aber man kann versuchen, den gleichen Befehl zu überspringen und so tun, als wäre nichts geschehen. In Blitrange wird nun abgefragt, wie lange es her ist, seit der letzte Fehler aufgetreten ist. Ist es weniger als 20ms her, so gehen wir davon aus, daß dieser und der letzte Busfehler zusammengehören, also von unseren Neustartversuch herrührt. Dann versuchen wir es solange weiter, bis die Gesamtzeit von zehn Sekunden überschritten wird. Ist dies der Fall, so brechen wir unseren Versuch ab, um den Blitter nicht weiter zu quälen. Es folgt eine normale Busfehlerbearbeitung (Ade, Du schönes Bild). Es soll an dieser Stelle nicht verschwiegen werden, daß GOODBLIT einen bekannten Fehler nicht abfängt, da sonst das Betriebssystem durcheinander gerät: Zeichnet man einen geclippten Kreis, der sehr groß ist, so kann dies schon einmal zum Absturz führen, so be careful where you Step...
Der eigentliche Neuaufruf in REPAIR besteht darin, daß wir den Fehler-PC auf den Stack (und natürlich unsere Register wieder in Ordnung) bringen, d.h. wir benutzen nicht die Adresse, durch die der Fehler passierte, sondern die nächste Adresse, welche ja die Fetch-Adresse ist (den Satz sollten Sie sich noch einmal langsam durchlesen). Beim RTE-Befehl wird dann als Rücksprungadresse der Fetch-PC benutzt und dieser Befehl angesprungen.
Zuletzt noch ein kleines Wort zu DO_BUSERROR. Der JMP-Befehl enthält im Listing den Wert $FFFFFFFF, was völlig korrekt ist. In der Initialisierung, die wir wie gesagt nächsten Monat genauer untersuchen werden, wird der Befehl so verändert, daß an dieser Stelle dann die Adresse der Routine steht, die die Original-Busfehlerverarbeitung durchführt. Mit einigem Fleiß müßte es Ihnen nun eigentlich möglich sein, die Erste-Hilfe-Maßnahmen für unseren Blitter zu verstehen und ich hoffe, daß Sie auch Ihrem Blitter damit wieder auf die Beine helfen können - er hat es verdient, den eigentlich ist er ja ein netter Kerl.
Zum Schluß möchte ich noch Jörg Drücker von IMAGIC-Grafik danken, dessen Ideen ich hier in hoffentlich allgemeinverständlicher Form zu Papier gebracht habe. Ihm ist die schnelle Genesung unseres Blitters zu verdanken (Blit, Blit, Hurra).
*
* --------------------------
* | GOODBLIT |
* --------------------------
*
* BLITTER Fehler-Korrektur
*
* ASM source code
*
* ---------------------------------------------------
*
* (c) 1988 by IMAGIC Grafik GbR.
*
* Alexander Beller.
* Joerg Drücker .
*
* Waldenbucherstr. 53. 7447 Alchtal-Aich.
*
* geschrieben 13-2-88 by Jörg Drücker,
* verändert 18-2-88 Fehler-Fenster verfeinert
*
* ===================================================
*
* Dieses Programm installiert sich selbst RESIDENT im
* Speicher und versucht den Blitterbusfehler zu beheben.
*
* Falls der Fehler länger als 10 Sekunden dauert, dann
* wird das aktuelle Programm unterbrochen.
*
* Alle anderen Busfehler werden nicht beachtet.
*
* ---------------------------------------------------
*
* Anm.: Dieses Programm ist f.d. AUTO-Ordner gedacht.
* kann aber auch direkt von DESKTOP aus gestartet werden.
*
* ===================================================
*
* OS-Konstanten
*
* TRAPS:
GEMOOS: equ 1
BIOS: equ 13
XBIOS: equ 14
*
* GEMDOS:
P_TERM0: equ 0
C_CONWS: equ 9
P_TERMRES: equ $31
M_SHRINK: equ $4A
*
* XBIOS:
SUPEREXEC: equ 38
*
HZ_200: equ $4BA * 200 Hz Sytem-Zähler
*---------------------------------------------------*
RESID_A: bra INSTALL
*--------------------------------*
*
* GOODBLIT
* --------
*
* Blitterbusfehler beheben
*
*--------------------------------*
dc.b 'GB10' * "instal1iert" Merker
GOODBLIT: move.w sr,BLIT_TAB * Statusregister retten
move.w #$2780,sr * keinen anderen Interrupt!
movem.l d0-d2/a0,BLIT_TAB+2 * Register absolut retten
*---------------------------------------------------*
*
* check #1: Har es ein FETCH-Supervisor-Befehl
move.w (sp),d0 * super-Statuswort
* andi.w #$1F,d0 * maskiere interessante Bits
andi.w #$17,d0 * Bit 3 immer Null
cmpi.w #$16,d0 * check Statuswort
bne DO_BUSERR
*---------------------------------------------------*
*
* check #2: war fetch-Adresse ln der Nähe der Fehler-Adresse
move.l 2(sp),d0 * Fehler-Adresse ( Fetchadresse )
sub.l 10(sp),d0 * subtrahiere Fehler-FC
bcs DO_BUSERR * Fetch-Adresse< Fehler-FC
cmpi.l #11,d0 * d0 <= 10 Bytes ?
bcc DO_BUSERR * gröBerer Abstand!
*---------------------------------------------------*
*
* check #3: Zeigt Irgendein Register auf den Blitter ?
move.l #$FFBA00,d1 * BLITTER Adresse
moveq #7,d2 * 8 Adressregister
CHKREGS: andi.w #$FFF8,OPCODE * Lösche Register-Nummer in Opcode
or.w d2,OPCODE * Set Register-Nummer in Opcode
nop * Vermeide einen Prefetch!
nop
OPCODE: move.l a0,d0 * Dieser Opcode wird verändert,..
andi.l #$FFFFFF,d0 * nur 24 Bits Interessant
sub.l d1,d0 * Ax - $FF8AB0
bcs.s NEXTREG * Ax < $FF8A00
cmpi.l #$3F.d0 * Bereich 0..$3F
bcs.s BLITRANGE * <* $3F: Blitter-Adressbereich
NEXTREG: dbra d2,CHKREGS
*---------------------------------------------------*
*
* check #4: Uar es eine Absolutadressierung des Blitters
*
* Falls PC nicht erhöht: Adresse liegt im Bereich 0..+6 des PCs
* Falls PC erhöht: Adresse liegt im Bereich -4..-8 des PCs
*
* Also Bereich von -4 bis +6 testen
moveq #14,d2 * Bereichsgröße
movea.l 10(sp),a0 * PC-Adresse als Fehler auftrat
CHKABS: move.l -8(a0,d2.w),d0 * überprüfe Absolute Adressierung
andi.l #$FFFFFF,d0 * 24-Bits
sub.l d1,d0 * d(pc) - $FF8A00
bcs.s NEXTABS * d(pc) < $FF8A00
cmpi.l #$3F,d0 * Bereich 0..$3F
bcs.s BLITRANGE * <= S3F: Blitter-Adressbereich
NEXTABS: subq.w #2,d2 * erniedrige Zeiger
bpl.s CHKABS
bra.s DO_BUSERR * Keine Adresse des Blitters
* gefunden
*--------------------------------------------------------*
*
* Es war ein Blitterfehler!
* korrigiere Fehler und versuche es noch einmal!
BLITRANGE: lea BLIT-TAB(pc),a0 * Zeiger auf BLIT.TAB
move.l HZ_200,d2 * Aktueller 208Hz-Timer-Wert
move.l d2,d1
sub.l 18(a0),d1 * Subtrahiere ZeltHert
* als der letzte Fehler
move.l d2,18(a0) * auftrat und speichere aktuellen Wert
subq.l #3,d1 * länger als 20ms her?
bcc.s LONGER_TIHE * Ja, setze neue
* START-Zeit
move.l d2,d1 * schneller als 20ms!
sub.l 22(a0),d1 * subtrahiere Start-Zeit
cmpi.l #2000,d1 * Insgesamt 10 Sek.?
bcs.s REPAIR * nein, repariere Bus-Fehler
tst.l 22(a0) * Zeit ungleich Null?
bne.s DO_BUSERR * Ja, breche Programm ab!
LONGER_TIME: move.l d2,22(a0) * setze neue Start-Zeit
*-------------------*
* repariere Bus-Fehler
REPAIR: move.l 2(sp),10(sp) * springe zur Fetch-Adresse
addq.l #8,sp * vernachlässige Fehler-Information
movem.l BLIT_TAB+2(pc),d0-d2/a0
rte * versuche es noch einmal...
*---------------------------------------------------*
*
* Kein Blitterfehler, einfach normalen Bus-Fehler ausführen!
DO_BUSERR: movem.l BLIT_TAB+2(pc),d0-d2/a0
move.w BLIT_TAB(pc),sr
JMP_BUSERR: jmp $FFFFFFFF * Opcode wird modifiziert
BLIT_TAB: ds.w 1 * Status-Register
ds.l 4 * Die vier gespeicherten Register
dc.l 0 * Fehler-Differenzzeit
dc.l 0 * Fehler-Startzeit
RESID_B:
*--------------------------------------*
* *
* Haupt-Programm *
* *
*--------------------------------------*
INSTALL: move.l sp,a6 * System-Stack-Pointer
lea USTACK,sp * Installiere Benutzer-Stack
move.l 4(a6),a6 * Adresse der BASEPAGE
move.l $C(a6),a4 * TEXT Segment-Länge
adda.l $14(a6),a4 * + DATA Segment-Länge
adda.l $1C(a6),a4 * + BSS Segment-Länge
pea 256(a4) * + BASEPAGE-Länge
pea (a6) * BASEPAGE
clr.w -(sp) * dummy
move.w #M_SHRINK,-(sp) * verkleinere benutzten Speicher
trap #GEMDOS
lea 12(sp),sp * Stack korrigieren
pea INSTAL_GOOOBLIT(pc) * Installiere GOODBLIT
move.w #SUPEREXEC,-(sp)
trap #XBIOS * In Supervisor-Mode ausführen
addq.l #6,sp
move.w INSTAL_FLAG(pc),d0 * Klappte Installierung
bne.s ITS_DONE * JA, Text ausgeben.
* resident bleiben
move.w #P_TERM0,-(sp) * Programm ohne Anmerkung verlassen
trap #GEMDOS
*===================================*
ITS_DONE: pea IO(pc)
move.w #C_CONWS,-(sp)
trap #GEMDOS
addq.l #6,sp
clr.w -(sp) * Returnwert 0
pea RESID_B-RESID_A*256 * minimale Resident-Länge
move.w #P_TERMRES,-(a7)
trap #GEMDOS
*----------------------------------*
INSTAL_GOODBLIT:
ori.w #$700.sr * IPL 7. Interrupts ausschalten
lea 8,a1 * Bus-Fehler-Zeiger
movea.l (a1),a0 * Original-Wert holen
cmpi.l #'GB10',-(a0) * GOODBLIT schon instal.?
beq.s INSTALLED * scheint so! -> Prg.ende
lea JMP_BUSERR+2(pc),a0 * Adresse der
INSTALLED:
rts
*----*
INSTAL_FLAG: dc.w 0 * Enthält FF, wenn es schon installiert ist
*---------------------------------------*
* Ausgabe-Text
ID: dc.b $1B,'E'
dc.b 'GOOD BLIT ! ',$BD,' 1988 by IMAGIC Grafik.'
END_ID: dc.w 0
*---------------------------------------*
bss
ds.b $100
USTACK: ds.w 0
end