ST-ECKE: GOOD-BLIT! Erste Hilfe für den Blitter...

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 !

Der Sache auf den Grund gegangen: Die Symptome

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.

Der Sache auf den Grund gegangen: Die Diagnose

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.

Ein Bus auf Abwegen

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, das Programm

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).

Ein Trick für Assemblerfreaks

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.

Der Sache auf den Grund gegangen: Die Heilung

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

Stefan Höhn
Aus: ST-Computer 06 / 1988, Seite 164

Links

Copyright-Bestimmungen: siehe Über diese Seite