Bekanntlich können Computer und Programme gar nicht schnell genug sein, um eingefleischte Hacker zu befriedigen. Dies gilt nicht zuletzt auch für die Bildschirmausgabe, die Teil des Betriebssystems und darum nicht so einfach durch eigene Routinen zu ersetzen ist. Wie man die Textausgabe um bis zu Faktor sechs beschleunigt und dergleichen mehr, zeigt ein in Assembler geschriebenes Programm, dessen erster Teil in dieser Ausgabe der ST Computer veröffentlicht wird.
Mag sein, daß es etwas ungewöhnlich ist, den Anfang mit dem Ende zu verbinden (Sokrates’ Lieblingsbeschäftigung einmal ausgekreist), aber da dieser Artikel ob seiner Länge nur häppchenweise publiziert werden kann, sollten Sie wenigstens jetzt schon erfahren, was Sie darin erwartet und was Sie damit anfangen können. Es handelt sich bei xVT52.PRG um einen Patch des für “normale” Textausgabe (also solche ohne Attribute) zuständigen VT52-Emulators. Das heißt, es ist eigentlich kein Patch mehr, sondern viel mehr ein komplett neuer Emulator, denn wenn er erst einmal installiert ist, kann sich der Origina1-Emulator auf Rente begeben, weil keine einzige Funktion des TOS ihn mehr eines Bytes würdigt. Dafür ist das komplett in Profimat-Assembler geschriebene Programm wesentlich leistungsfähiger im Funktionsumfang und auch bis zu sechsmal schneller als sein in C geschriebener Kollege. C erzeugt sicherlich kompakten und recht schnellen Code, wenn man der Perversion übersichtlicher Programmierung mächtig ist, aber schon das Runtime-Gerüst eines C-Programmes ist bei weitem größer als der komplette xVT52-Emulator, und was die Geschwindigkeit angeht, spricht ein Zeitvergleich zwischen altem und neuem Emulator für sich.
Sind Sie Mitglied der immer größer werdenden DFÜ-Fan-Gemeinde? Dann haben Sie es bestimmt schonmal vermißt, daß der VT52 zwar ASCII-Texte übermitteln kann, nicht aber Grafiken. Häufig kommt es auch vor, daß man eben mal einen Text unterstreichen möchte, um ihn hervorzuheben. Vor derlei Sonderwünsche hat das TOS das VDI gesetzt; und das ist nun nicht eben ein Ausbund an Bedienungsfreundlichkeit. In aufwendigeren Tabellen kann es auch nötig werden, mehr als nur eine Cursorposition zwischenzuspeichern (mit ESC j); hatte man doch keine legale Möglichkeit im VT52, an die derzeit adressierte Spalte/Zeile heranzukommen... Ein häufig geäußerter Benutzerwunsch ist auch das pixelweise Verschieben des Bildschirmbereiches (Finescrolling genannt), das sich nun sehr einfach erreichen läßt - sofern Sie zu den achtzig Prozent der ST-Besitzer zählen, die einen SM 124 ihr monochromes eigen nennen. Dagegen spielt es keine Rolle, ob nun ein 260er oder ein Mega ST4 Ihren Schreibtisch ziert: xVT52 läuft auf allen Konfigurationen und TOS-Versionen.
Im übrigen mag Ihnen diese Serie dazu dienen, das eine oder andere dazuzulernen, derweil zumindest die wichtigsten Programmteile nochmal ausführlich auseinandergenommen werden, um ihre Funktionsweise zu erläutern.
Für den Anwender reduziert sich der Aufwand zum Installieren des Programmes auf einen Maus-Doppelklick, den man sich aber auch ersparen kann, wenn man xVT52.PRG in den AUTO-Ordner des Bootlaufwerks legt. Um den Emulator im Betriebssystem zu verankern, ist allerdings erheblicher Aufwand zu betreiben: nicht weniger als alle vom TOS benutzten TRAP-Vektoren sind umzubiegen, um alle Aufrufe abzufangen, die irgendwas mit Bildschirmausgabe zu tun haben. Und das sind derer reichlich viele...
Wie man so im allgemeinen bestimmte TOS-Funktionen “pätscht”, wurde in zahlreichen Publikationen bereits demonstriert: man legt den zum TOS-Bereich gehörigen Vektor (z.B. $84 für GEMDOS) auf eine eigene Routine, in der man die auf dem Stack befindliche Funktionsnummer dahingehend untersucht, ob sie mit der zu patchenden übereinstimmt. Ist dem so, ruft man die eigene, ansonsten die Originalroutine auf. Klingt einfach und plausibel? Ist es auch - sofern man Funktionen von GEMDOS, BIOS und XBIOS ändern möchte. Beim Versuch, AES/VDI-Funktionen auf diese Weise zu manipulieren, könnte die Mega-atarianische Selbstmordrate leicht in ungeahnte Höhen steigen: alle Marvins der Galaxis zusammen könnten nicht deprimierter sein als ein programmierender Erdling, der es einfach nicht begreifen kann, warum sein ST Dinge tut, die er eigentlich gar nicht tun dürfte - und so sieht’s aus: man biegt den AES/VDI-Trap-Vektor an Adresse $88 auf eine eigene Routine um (wie es oben geschildert wurde) und ruft probehalber die geänderte Funktion auf. Und siehe da: es funktioniert. Derart begeistert, probiert man’s gleich nochmal... Und siehe da: es funktioniert nicht. Aha. Sicherheitshalber Resetknöpfchen drücken und nochmaliges Installieren sind eins; der anschließende Test ergibt das erwartete Ergebnis. Es klappt. Wenn man Glück hat, klappt’s sogar noch ein paarmal, um dann aber widersinnigerweise wieder nicht zu klappen, obwohl auch die tausendste Analyse des Listings nicht den Hauch eines Fehlers zutage fördern konnte. Der Ausdruck: “Klappt nicht” ist übrigens dahingehend zu verstehen, daß nicht etwa Bombenteppiche den entgeistert auf den Bildschirm gerichteten Blick weich abzufangen versuchten - nein, das wäre auch zu trivial -, es ist nur einfach so, daß es ganz danach aussieht, als liefe ein total anderes Programm als das eingegebene. Verwirrt? Keine Panik - es gibt eine Lösung. Sie ist eigentlich verblüffend einfach, bloß muß man erst mal draufkommen: durch einen glücklichen Zufall kam heraus, daß der Vektor, der - logisch betrachtet -auf die eigene Routine hätte zeigen müssen, perfiderweis’ wieder ins ROM und somit auf die Originalroutinen zeigte!!! Wenn Sie mir jetzt einreden möchten, ich sei von Blind-und/oder Blödheit geschlagen, wenn ich noch nicht mal merken würde, daß nicht mehr meine, sondern die Originalroutinen ausgeführt werden, möchte ich Ihnen zwar nicht grundsätzlich widersprechen, aber zu bedenken geben, daß es sich bei den entsprechenden Routinen um solche handelte, die zum Original kompatibel sind und sich also nicht so besonders gut unterscheiden lassen... Ich hoffe, daß wenigstens den Programmierern von Digital Research der Sinn bekannt ist, warum der GEM-Vektor so ab und an immer mal wieder ins ROM geschubst wird. Daß hierzu auch noch der jedes Programm unglaublich übersichtlich und lesbar machende Line-F-Emulator Verwendung findet, legt die Vermutung nahe, unter den VDI-Programmierern sei ein Sadist gewesen...
Da es außer TRAP #2 keine weitere Schnittstelle zum GEM gibt, mußte der ominöse Pointerverbieger aus GEM selbst kommen; da der Vektor jedoch gar nicht mehr aufs Original zeigte und trotzdem wieder auf die ROM-Adresse umgesprungen wurde, konnte es nur noch eine Interrupt-Routine sein, die da ihr wüstes Unwesen treibt. So ist’s denn auch: der erste Slot des Vertical-Blank-Interrupts ist für GEM reserviert, und inmitten dieser IR-Routine residiert der Unhold. Weil ich mich ganz nett über den Typen geärgert hab’, verzichtete ich darauf herauszufinden, unter welchen Umständen er in Aktion tritt. Falls es jemand herausgefunden haben sollte (es ist ja durchaus auch eine sehr einfache Erklärung möglich), möge er sich doch bitte melden. Danke.
Die Lösung des Problems war jetzt eigentlich nur noch Formsache und kann in der Cursor-Interrupt-Routine CRS_IRR nachgelesen werden: der Vektor wird auf die eigene Routine gebogen; war er zwischenzeitlich ins ROM gewandert, wird der Cursor ausgeschaltet. Die Praxis hat gezeigt, daß dies so sinnvoll ist; richtig begründen kann ich’s aber nicht, weil ich, wie gesagt, keine Lust hatte, in den unergründlichen Tiefen des VDI herumzustöbern (wo Line-F-Emulator und ähnlich gräßliche Dinge zu Hause sind...). Bemerkenswert ist noch, daß die Vektormanipulation innerhalb der Interrupt-Routine herzerfrischend unanständig ist: wenn GEM auf die hinterlistige Idee kommt, den Vektor ins ROM zu legen, kann es dies nur während des Vertical-Blank-Interrupts in die Tat umsetzen, wobei es die allererste Routine innerhalb der VBL-Schlange benutzt. Die Routine, die den Vektor wieder ins RAM biegt, kommt direkt dahinter zur Ausführung, ohne daß GEM etwas davon wüßte oder es gar verhindern könnte. Ganz schön fies, gell? Und da sich das alles während des VBL-Interruptes abspielt und also zwischenzeitlich kein Programm aktiv sein kann, wird auf diese Tour sichergestellt, daß GEM-Aufrufe im Patch und nicht im ROM landen. Schubidu. Sollten Sie irgendwann einmal in die Verlegenheit kommen, Veränderungen im GEM vorzunehmen (z.B. weil Ihnen die File-Select-Box nicht mehr gefällt oder Sie mehr als vier Fenster haben möchten...), würde ich dringend empfehlen, ebenfalls diese “Technik” zu verwenden; sie hat sich bisher als ebenso problemlos wie zuverlässig erwiesen.
Nachdem der “Brocken” nun aus dem Weg geräumt ist, läßt sich der Rest der Installation wesentlich lockerer nachvollziehen. Als da wäre: Reservieren des benötigten Speicherplatzes, Einfügen der Cursor-Interrupt-Routine in die VBL-Slots sowie Umbiegen der Vektoren für Aufrufe von GEMDOS, AES/VDI, BIOS und XBIOS.
Da - wie bereits erwähnt - alle vier Teile des Betriebssystems über Funktionen zur Textausgabe via VT52 verfügen, mußten dementsprechend auch neue Traphandler geschrieben werden (im Listing tragen sie die ungewöhnlichen Namen GEMDOS, AES_VDI, BIOS und XBIOS). Ihre Lebensaufgabe besteht darin, TOS-Aufrufe dahingehend zu analysieren, ob die angeforderte Funktion zu patchen ist. Wenn ja, werden die Daten-und Adreßregister des Prozessors gerettet, die neugeschriebene Routine angestoßen und die Register wieder restauriert. In allen anderen Fällen wird die Programmkontrolle an den Origina1-Traphandler weitergegeben, dessen Adresse beim Installieren der eigenen Routinen ja gemerkt wurde. Obwohl durch das Patchen pro Aufruf etwas mehr Code abgearbeitet werden muß, ist keine Verlangsamung des Systems zu befürchten. In der folgenden Übersicht sind alle Funktionen aufgelistet, die von xVT52 abgefangen werden, wobei ein Sternchen darauf hinweist, daß diese Funktionen neu hinzugekommen sind.
GEMDOS:
VDI:
BIOS:
XBIOS:
Wie man sieht, wird die Wichtigkeit der textuellen Ausgabe von TOS mit einem ziemlichen Funktionsaufgebot gebührend gewürdigt und so nimmt es nicht Wunder, daß xVT52 mit seinen über 1800 Zeilen nicht eben zu den Programm-Winzlingen gehört... Die neue Betriebssystemfunktion XBIOS 100 dient dazu, die Ausgabe in einen definierten Zustand zu bringen. Die im Listing mit INIT_CON-OUT bezeichnete Routine, die auch mit ESC i aktiviert werden kann, wird während des Installierens aufgerufen und beginnt ihren Dienst mit dem Retten der Zeiger auf die beiden Systemfonts der Kategorie 8x8 und 8x16, wobei zugleich letzterer zum aktuellen Zeichensatz erhoben wird. Der Grund liegt zum einen darin, daß die Fontdaten für die Ausgabe zwingend benötigt werden, andererseits bietet xVT52 auch die Möglichkeit, mit einer einfachen Escape-Sequenz zwischen den beiden Fonts hin- und herzuschalten! Desweiteren werden die Ausgabe-Attribute wie Wrapping, inverse Darstellung, Blinkfrequenz des Cursors etc. auf ihre Defaultwerte gebracht. Vor allem aber wird der “Ausgabevektor” initialisiert. Was es mit diesem auf sich hat, erfahren Sie im folgenden Kapitel.
Vektoren - neudeutsch Pointer - sind nichts anderes als Speicherbereiche (beim MC68000 sind sie 4 Bytes lang), die eine Adresse enthalten und auf “irgendwas” zeigen. So z.B. auf Variablen, Funktionen oder auch auf weitere Pointer. Die Tatsache, daß man Pointer nicht so einfach verfolgen und manipulieren kann wie normale Variablen, führt gewöhnlich dazu, daß dieses Kapitel beim Erlernen einer Programmiersprache meistens auf dem letzten Platz der Beliebtheitsskala rangiert. Dennoch sind sie absolut unverzichtbar, denn egal, ob man sie in “getarnter” Form (wie in Pascal durch Voransetzen des Schlüsselwortes VAR vor ein Variable oder Prozedur/Funktion innerhalb einer Übergabe liste) oder explizit (wie in C durch den Stern-Operator, z.B. char * string) benutzt: ohne sie geht nichts. Auch nicht bei der Textausgabe. Unter den TOS-Variablen findet man an der Adresse $4A8 einen dieser Zeiger, dessen Zweck mit “Interner Zeiger für Bildschirmausgaberoutinen” angegeben ist. Ich nehme an, daß diese Art kryptischer Erklärungen nicht ganz unschuldig am weitverbreiteten Pointer-Desinteresse ist, weshalb ich ihn etwas genauer beschreiben möchte. Wenn Sie irgendeinen Text auf den Bildschirm ausgeben möchten, rufen Sie hierzu eine TOS-Funktion auf. In dieser wird geprüft, ob der derzeit verwendete Zeichensatz das gewünschte Zeichen enthält. Wenn ja, wird es dargestellt, die Cursorposition erhöht und evtl, der Bildschirminhalt nach oben gescrollt. Damit kann man schon ganz gut leben, bloß muß es auch Möglichkeiten geben, die Ausgabe zu steuern (Bildschirm ganz oder teilweise löschen etc.). Für diese Aufgaben stellt der im TOS vorhandene VT52 Emulator die sog. ESC-Sequenzen zur Verfügung, d.h. durch das ASCII-Zeichen 27 eingeleitete Strings. Für den Emulator bedeutet dies, daß er dieses sowie die nachfolgenden Zeichen nicht einfach ausgeben, sondern interpretieren soll. Dies wird so gelöst, daß die Ausgaberoutine des Betriebssystems nicht direkt angesprungen wird, sondern eben über einen Vektor, der im Normalfall auf diese zeigt. Beim xVT52 Emulator heißt dieser Vektor VEC_BASE und enthält die Adresse der Standard-Ausgaberoutine STD_VEC. Sobald das ESC-Zeichen erkannt wird, wird der Vektor auf eine andere Routine (ESC_SEQ) umgebogen, die dann die gewünschte Funktion in Abhängigkeit des nächsten Zeichens aktiviert. Ohne den “Trick” mit dem Vektor wäre dies -wenn überhaupt-nicht so elegant zu lösen!
In der nächsten Folge werde ich damit beginnen, die neu hinzugekommenen ESC-Sequenzen unter die Lupe zu nehmen und Ihnen erklären, warum die Textausgabe nun viel schneller geworden ist. Die Zwischenzeit können Sie ja mit dem Eintippen des ersten Teils des Listings überbrücken...
MS
; *************************************
; * >EXTENDED VT52-TERMINAL EMULATOR< *
; * Entwickelt mit PROFIMAT-Assembler *
; * M.Schumacher, (p) 12/87 *
; *************************************
LOGBASE equ $44E ; Zeiger auf logischen Bildschirm
VID_R0 equ $FFFF8240 ; Video-Farbregister 0
REGISTER equ d3-d7/a0/a3-a6 ;benötigte Register für Verschieberoutinen
RESETCO equ INIT_CONOUT ; Termina1-Initialisierungs-Routine
NVBLS equ $454 ; Anzahl VBL-Routinen
VBLQUEUE equ $456 ; Zeiger auf Zeiger auf VBL-Routinen
text
INSTALL: move.l 4(a7),a4 ; ^Basepage
move.l 12(a4),d7 ; Länge TXT
add.l 20(a4),d7 ; +DTA
add.l 28(a4),d7 ; +BSS
add.l #256,d7 ; +256
move.l d7,-(a7) ; = Anzahl zu reservierender Bytes
clr.l -(a7) ; SUPER ON
move.w #$20,-(a7)
trap #1
move.l d0,2(a7) ; alten SSP merken
bsr INIT_CONOUT ; Emulator initialisieren
move.l VBLQUEUE,a0 ; AVBL-Zeiger
move.w NVBLS,d0 ; Anzahl der Routinen
subq.w #1,d0 ; in dbra-Zähler wandeln
\test_slot:
tst.l (a0) ; Slot frei?
beq.s \freeslot ; ja
addq.l #4,a0 ; sonst halt
dbra d0, \test_slot ; den nächsten Slot testen
bra.s \noslot ; kein freier Slot mehr?! (normal unmöglich)
\freeslot:
lea CRS_IRR,a1 ; Cursor-Interrupt-Routine
move.l a1,(a0) ; einbinden
\noslot:
lea TRAPS,a4 ; ^Speicher für Origina1-Trap-Handler
move.l $84, (a4)+ ; alten GEMDOS-Vektor merken
lea GEMDOS,a2 ; und neuen
move.l a2,$84 ; installieren
move.l $88, (a4)+ ; dasselbe mit GEM-Vektor
lea AES_VDI,a2
move.l a2,$88
move.l $B4,(a4)+ ; BIOS-Vektor
lea BIOS,a2
move.l a2,$B4
move.l $B8,(a4) ; XBIOS-Vektor
lea XBIOS,a2
move.l a2,$B8
trap #1 ; SUPER OFF
addq.l #6,a7 ; Stack aufräumen
move.w #$31,-(a7) ; KEEP PROCESS (hi zombie!)
trap #1 ; auf Wiedersehen im GEMDOS
;*****************************************
;* G E M D O S / A E S+V D I / B I O S / *
;* X B I O S - TRAPHANDLER *
;*****************************************
SAVE_REGS: ; Register retten
move.l (a7)+,d0 ; Rücksprungadresse retten
lea SAVE_TOP,a1 ; A1=Registerspeicher, Obergrenze
move.w (a7)+,-(a1) ; save (SR)
move.l (a7)+,-(a1) ; save(PC)
movem.l d3-d7/a3-a7,-(a1) ;save(Register)
lea 2(a0),a7 ; SSP auf 1. Parameter setzen
move.l d0,-(a7) ; Rücksprungadresse zurück
rts
REST_REGS: ; Register zurückholen
lea SAVE_BOT,a1 ; A1=Registerspeicher, Untergrenze
movem.l (a1)+,d3-d7/a3-a7 /Register zurück
move.l (a1)+,-(a7) ; PC zurück
move.w (a1)+,-(a7) ; SR zurück
rte ; zurück ins aufrufende Programm
SAVE_BOT: ; Untergrenze Zwischenspeicher
ds.w 23,0 ; Platz für Register, PC und SR
SAVE_TOP: ; Obergrenze Zwischenspeicher
GEMDOS:
movea.l a7,a0 ; AO-SSP;
btst #5,(a0) ; if (Aufruf aus S-Mode)
beq.s \from_user
addq.l #6,a0 ; then Offset addieren
bra.s \test_pline ; (PC.L+SR.W)
\from_user:
move.l USP,a0 ; else User Stack benutzen;
\test_pline:
cmpi.w #9, (a0) ; if (Funktion == PRINT LINE)
beq.s \pline ; then eigene Funktion benutzen
cmpi.w #64, (a0) ; else if (Funktion == WRITE)
beq.s \write ; then auf Ausgabe-Kanal prüfen
cmpi.w #2,(a0) ; else if (Funktion == CCONOUT)
beq.s \cconout ; then eigene Funktion benutzen
cmpi.w #6, (a0) ; else if (Funktion != CRAWIO)
bne.s \orig ; then Originalroutine rufen
cmpi.b #-1,3 (a0) ; else if (Zeichen — 255)
beq.s \orig ; then Original aufrufen
\cconout:
bsr SAVE_REGS ; Register retten
bsr CON_OUT ; Zeichen ausgeben
bra REST_REGS ; Register zurück, fertig
\pline:
bsr SAVE_REGS ; Register retten
bsr WRITE ; String ausgeben
bra REST_REGS ; Register zurück, fertig
\orig:
move.l TRAPS,a0 ; else Originalroutine
jmp (a0) ; benutzen
\write:
cmpi.w #1,2(a0) ; Ausgabe auf CON:?
bne.s \orig ; nein, ham wa nix mit zu tun
addq.w #2,a0 ; sonst Anzahl auszugebender
move.l 2(a0),d1 ; Zeichen holen
beq.s \retour ; kein Zeichen ist definitiv zu wenig!
bsr SAVE_REGS ; ansonsten Register retten
addq.w #4,a7 ; (a7) - String-Pointer
move.l d1,-(a7) ; Anzahl auf Stack retten
move.l 4(a7),-(a7) ; String-Pointer auf Stack legen
\lp:
move.l (a7),a0 ; String-Pointer holen
addq.l #1,(a7) ; auf nächstes Zeichen zeigen lassen
move.b (a0),d1 ; Zeichen holen
and.w #$FF,d1 ; nur LSB beachten
move.w d1,-(a7) ; Zeichen auf Stack legen
bsr CON_OUT ; und ausgeben
addq.w #2,a7 ; Stack korrigieren
subq.l #1,4(a7) ; Anzahl Zeichen dekrementieren
bne.s \lp ; und nächstes Zeichen ausgeben
addq.l #8,a7 ; Zähler und Pointer löschen
bra REST_REGS ; Register restaurieren und zurückspringen
\retour:
rte
AES_VDI:
cmpi.w #$73,d0 ; VDI-Aufruf?
bne.s \orig ; nein, AES
move.l a0,-(a7) ; a0 retten
move.l d1,a0 ; ^ParameterBlock
move.l (a0),a0 ; ^ContrlBlock
cmpi.w #5,(a0) ; VDI-ESC-Sequenz? (CONTRL[0))
bne.s \op_close ; nein
lea 10(a0),a0 ; ^Unterfunktionsnummer (CONTRL[5)
cmpi.w #16,(a0) ; <16?
bmi.s \ok ; ja, eigene Routine ausführen
cmpi.w #101,(a0) ; VDI ESC 101?
beq.s \ok ; ja
cmpi.w #102, (a0) ; Font installieren?
bne.s \fail ; nein, dann Originalroutine benutzen
\ok: ; *** eigene Routinen ausführen ***
lea VDI_TOP,a0 ; ^Zwischenspeicher
move.l (a7)+,-(a0) ; alten Inhalt von aO retten
move.w (a7)+,-(a0) ; SR
move.l (a7)+,-(a0) ; PC
movem.l d0-d7/a1-a7,-(a0) /alle Register retten
bsr VDI_ENTRY ; Routine ausführen
lea VDI_BOT,a0 ; ^Zwischenspeicher
movem.l (a0)+,d0-d7/a1-a7 ;Register wieder zurück
move.l (a0)+,-(a7) ; pc,
move.w (a0)+,-(a7) ; SR und
move.l (a0)+,a0 ; a0 restaurieren,
rte ; Exception beenden
\fail:
move.l (a7)+,a0 ; a0 restaurieren
\orig:
move.l TRAPS+4,-(a7) ; Originaladresse AES/VDI
rts ; benutzen
\op_close:
cmpi.w #100,(a0) ; openVirtualScreenWorkstation?
bne.s \close ; nein
lea TCB,a0 ; ^TerminalControlBlock
clr.l (a0) ; Zeile und Spalte auf 0
bra.s \fail ; zusätzlich Originalroutine ausführen
\close:
cmpi.w #101,(a0); CloseVirtualScreenWorkstation?
bne.s \clear ; nein
lea CCB,a0 ; ^CursorControlBlock
bclr #3,(a0) ; Cursor abschalten
bra.s \fail ; und Originalroutine ausführen
\clear:
cmpi.w #3,(a0) ; ClearWorkstation?
bne.s \fail ; nein
movem.i d0-d7/a1-a5,-(a7) ; Register retten
lea TCB, a0 ; ^TerminalControlBlock
bsr CLS ; Bildschirm löschen
movem.i (a7)+,d0-d7/a1-a5 ; Register zurück
bra.s \fail ; zusätzlich Origina1routine ausführen
VDI_BOT:
ds.w 35,0 ; Platz für d0-a7, SR & PC
VDI_TOP:
BIOS:
movea.l a7,a0 ; A0=SSP;
btst #5,(a0) ; if (Aufruf aus S-Mode)
beq.s \from_user
addq.l #6,a0 ; then Offset addieren
bra.s \test_wr_asc ; (PC.L+SR.W)
\from_user:
move.l USP,a0 ; else User Stack benutzen;
\test_wr_asc:
cmpi.w #12,(a0) ; if (Funktion != WRITE_ASC)
bne.s \test_bconout ; then auf BCONOUT testen
bsr SAVE_REGS ; else { Register retten;
bsr WRITE_ASC ; String ausgeben;
bra REST_REGS ; Register zurück; beenden }
\test_bconout: cmpi.w #3,(a0) ; if (Funktion == BCONOUT)
beq.s \test_dev ; then auf Ausgabegerät testen
\orig_bios:
move.l TRAPS+8, - (a7) ; else Originalroutine benutzen
rts
\test_dev:
addq.w #2,a0 ; Zeiger auf Gerätenummer setzen
cmpi.w #2, (a0) ; if (Ausgabegerät != Console)
bne.s \test_dev_vid ; then auf ASCII-Ausgabe testen
bsr SAVE_REGS ; else { Register retten;
bsr CON_OUT ; Zeichen ausgeben;
bra REST_REGS ; Register zurück; beenden )
\test_dev_vid:
cmpi.w #5, (a0) ; if (!ASCII_Ausgabe)
bne.s \orig_bios ; then Originalroutine benutzen
bsr SAVE_REGS ; else { Register retten;
bsr ASC_OUT ; Zeichen ausgeben;
bra REST_REGS ; Register zurück; beenden )
XBIOS:
movea.l a7,a0 ; A0=SSP;
btst #5,(a0) ; if (Aufruf aus S-Mode)
beq.s \from_user
addq.l #6,a0 ; then Offset addieren (PC.L+SR.W)
bra.s \test_cursconf
\from_user:
move.l USP,a0 ; else User Stack benutzen;
\test_cursconf:
cmpi.w #21,(a0) ; if (Funktion == CURSCONF)
beq.s \cursconf ; then eigene Funktion benutzen
cmpi.w #100, (a0) ; else if (Funktion INIT_SCREEN)
beq.s \init ; then Bildschirmausgabe initialisieren
move.l TRAPS+12,-(a7) ; else Originalroutine benutzen
rts
\cursconf:
bsr SAVE_REGS ; Register retten
bsr CURSCONF ; eigene Routine ansto_en
bra REST_REGS ; Register zurück, beenden
\init:
bsr SAVE_REGS ; Register retten
bsr.s INIT_CONOUT ; Ausgabe initialisieren
bra REST_REGS ; Register zurück, beenden
; ***********************************************
; * NEUE ROUTINEN FÜR VT52 UND VDI-ESCAPES *
; ***********************************************
INIT CONOUT: ; ESC 'i', Terminal initialisieren
dc.w $A000 ; Line A-Pointer holen
lea TCB,a2 ; TerminalControlBlock initialisieren
move.l 4(a1),a4 ; ^8*8-Font
move.l $4C (a4),28(a2) ; ^Fontdaten speichern
move.l 8(a1),a4 ; ^8*16-Font
move.l $4C(a4),a4 ; ^Fontdaten
move.l a4,24 (a2) ; speichern
move.l a4,32(a2) ; und 8x16 zum aktuellen Font machen
move.l #$4f0018,4(a2) ; max.Spalte=79, max.Zeile=24
move.b #2,8(a2) ; Wrapping ein-, inverse Darstellung ausschalten
clr.w 10 (a2) ; keine Cursor-Position gespeichert (ESC 'j')
move.l #$100500,36(a2) ; Zeichenhöhe 16 Pixels, 16*80 Bytes/Textzeile
lea LINE_A,a2 ; Line-A Adresse
move.l a0,(a2) ; merken
lea VEC_BASE,a2 ; Ausgabevektor initialisieren
lea STD VEC,a3 ; ^Standardausgabe
move.l a3,(a2) ; als Default übernehmen
lea CCB,a4 ; CursorControlBlock initialisieren
move.w #$200, (a4) ; Cursor ausschalten, Blinkmodus wählen
move.l #$140014,2 (a4) ; Blinkrate und -Zähler auf 20 stellen
move.l LOGBASE,6(a4) ; abs. Cursorposition auf Bildschirm-Anfang
lea BEL_ADR,a0 ; ^Speicher für Adresse Gong-Routine
move.l #$FC201C,d0 ; TOS-Adresse (Version vom 06.02.1986)
cmpi.w #$1986,$FC001A ; "altes" TOS?
beq.s \old_tos ; ja
move.l #$FC2270,d0 ; sonst Blitter-TOS-Adresse nehmen
\old_tos:
move.l d0, (a0) ; Vektor installieren
lea TCB,a0 ; ^TerminalControlBlock
bsr CLS ; Bildschirm löschen+Home
bsr DEF_TABS ; Tabulatoren auf default setzen
bra UPDATE_END ; Cursor freigeben und zurück
CRS_IRR: ; Cursor-Interruptroutine
move.l $88,d1 ; derzeit aktive AES/VDI-Adresse
lea AES_VDI,a0 ; eigenen Trap-Handler neu installieren
move.l a0,$88 ; AES/VDI überlisten (grins)
cmp.l a0,d1 ; hat sich Adresse geändert?
bne UPDATE_CRS ; dann Cursor abschalten
lea CCB,a4 ; ^CursorControlBlock
btst #3,(a4) ; Cursor eingeschaltet?
beq.s \irr_end ; nein, fertig
btst #0,(a4) ; wird gerade Text ausgegeben?
beq.s \irr_end ; ja, dann nicht stören
btst #1,(a4) ; Blinken eingeschaltet?
bne.s \blink ; ja
btst #2, (a4) ; Cursorposition schon invertiert?
bne.s \irr_end ; ja, nicht mehr invertieren
\inv:
bsr CUR_INV ; Cursorposition invertieren
\irr_end:
rts ; zurück zum IR-Slot-Handler
\blink:
subq.w #1,4(a4) ; Blinkzähler —
bne.s \irr_end ; noch nicht 0, fertig
move.w 2(a4),4(a4) ; sonst Zähler neu laden
bra.s \inv ; invertieren und zurück
CURSCONF: ; XBIOS (21)
bsr UPDATE_CRS ; Cursor abschalten
move.w 4(a7),d1 ; Funktionsnummer holen
bne.s \f1 ; nicht 0
bclr #3,(a4) ; Cursor ausschalten
bra.s \zurück ; fertig
\f1:
cmpi.w #1,d1 ; Fkt.-Nr. 1?
bne.s \f2 ; nein
bset #3,(a4) ; Cursor einschalten
bra.s \zurück
\f2:
cmpi.w #2,d1 ; Fkt.-Nr. 2?
bne.s \f3 ; nein
bset #1,(a4) ; Cursor in Blinkmodus versetzen
bra.s \zurück
\f3:
cmpi.w #3,d1 ; Fkt.-Nr. 3?
bne.s \f4 ; nein
bclr #1,(a4) ; Blinkmodus ausschalten
bra.s \zurück
\f4:
cmpi.w #4,d1 ; Fkt.-Nr. 4?
bne.s \f5 ; nein
move.w 6(a7),2(a4) ; Blinkrate setzen
move.w 6(a7),4(a4) ; Zähler initialisieren
bra.s \zurück
\f5:
cmpi.w #5,01 ; Fkt.-Nr. 5?
bne.s \zurück ; nein, ignorieren
clr.l d0 ; d0.L wegen Wortoperation löschen
move.w 2(a4),d0 ; Blinkrate holen
\zurück:
move.l d0,-(a7) ; Rückgaberegister retten
bsr UPDATE_END ; Cursor wieder freigeben
move.l (a7)+,d0 ; Register zurück
rts
WRITE_ASC: ; String ausgeben (ohne Steuerzeichen)
move.l 4(a7),a6 ; ^Ausgabestring
\lp:
move.b (a6)+,d0 ; auszugebendes Byte holen
beq.s \ende ; falls 0: fertig
bsr.s ASC_ENTRY ; ausgeben
bra.s \lp ; bis String-Ende
\ende:
rts
ASC_OUT: ; DIREKTE ZEICHENAUSGABE(OxOO-OxFF)
move.w 4(a7),d0 ; Zeichen holen
ASC_ENTRY:
andi.w #$FF,d0 ; nur Byte beachten
bra PUT ; und ausgeben
WRITE: ; String ausgeben (mit Steuerzeichen)
move.l 4 (a7),a6 ; ^Ausgabestring
\lp:
move.b (a6)+,d1 ; auszugebendes Byte holen
bne.s \aus ; ausgeben, falls<>O
lea TCB+9,a0 ; ^Grafik-Flag
tst.b (a0) ; Grafik eingeschaltet?
beq.s \ende ; nein, fertig
\aus:
move.b d1,d0 ; Zeichen ausgeben
bsr.s CON_ENTRY
bra.s \lp ; bis String-Ende
\ende:
rts
CON_OUT: ; AUSGABE MIT STEUERZEICHEN
move.w 4(a7),d0 ; Zeichen holen
CON_ENTRY:
andi.w #$FF,d0 ; LSB isolieren
lea VEC_BASE,a1 ; ^Vektor
move.l (a1),a0 ; Vektor holen
jmp (a0) ; und anspringen
STD_VEC:
cmpi.w #" ",d0 ; Zeichen < Blank?
bpi PUT ; nein, ausgeben
cmpi.w #$1B,d0 ; ESC?
bne.s \control ; nein
lea ESC_SEQ,a0 ; AESC-Handler
move.l a0,(a1) ; als Vektor für nächstes Zeichen merken
\return:
rts ; fertig
\control:
subq.w #7,d0 ; <7?
bmi.s \return ; wenn ja, dann nicht beachten
cmpi.w #7,d0 ; >13?
bpl.s \return ; ja: Ausgabe unterdrücken
lea BEL,a1 ; ^dingelingeling
add.w d0,d0 ; Zeichenoffset in Word-Pointer umwandeln
move.w CTRL(PC,d0.w),a2 ; entsprechenden Vektor
add.l a1,a2 ; +Offset 1. Routine
lea TCB,a0 ; ^TerminalControlBlock
bsr.s UPDATE_CRS ; Cursor abschalten
jsr (a2) ; ausführen
bra.s UPDATE_END ; Cursor wieder einschalten
CTRL: dc.w 0 ; Glocke
dc.w BS-BEL ; Backspace
dc.w TAB-BEL ; Tabulator
dc.w LF-BEL ; Zeilenvorschub
dc.w LF-BEL ; Vertikaltabulator (wie Zeilenvorschub)
dc.w LF-BEL ; Formularvorschub (wie Zeilenvorschub)
dc.w CR-BEL ; Wagenrücklauf
UPDATE_CRS: ; Cursor für Textausgabe ausschalten
lea CCB,a4 ; ^CursorControlBlock
bclr #0,(a4) ; disable Cursor
btst #2,(a4) ; Cursor sichtbar?
beq.s \return ; nein
bra CUR_INV ; sonst Cursorposition invertieren
\return:
rts
UPDATE_END: ; Cursor wieder freigeben
lea TCB,a0 ; ^TerminalControlBlock
move.l LOGBASE,a1 ; ^Video-RAM
move.w 38(a0),d0 ; Bytes/Textzeile
mulu 2(a0),d0 ; * akt. Zeile
adda.l d0,a1 ; + Bildschirm-Anfang
adda.w (a0),a1 ; + akt. Spalte
move.l a1,6(a4) ; = abs. Cursorposition
bset #0,(a4) ; enable Cursor
rts
ESC_SEQ: ; Esc-Sequenz verarbeiten; Einsprung mit:
; d0.w = Steuerzeichen; a1 = AVEC_BASE
lea VEC_BASE, a1 ; Ausgabe-Vektor
lea STD_VEC,a0 ; wieder auf die normale Ausgabe biegen
move.l a0,(a1) ; umschalten
subi.w #"A",d0 ; Offset abziehen
bmi.s \esc_fail ; zu klein, ungültig
cmpi.w #"Z"-"A",d0 ; <="Z"?
ble.s \esc_big ; ja, entsprechende Routine ausführen
bra.s \esc_sml ; sonst auf Kleinbuchstaben testen
\esc_fail:
rts
\esc_big:
bsr.s UPDATE_CRS ; Cursor ausschalten
lea CRS_UP,a1 ; Adresse der 1. Routine
add.w d0,d0 ; Zeichen als Wort-Offset
lea E_BIG,a2 ; Adress-Tabelle
move.w 0(a2,d0.w),a2 ; Adress-Offset holen
adda.l a1,a2 ; + 1. Routine
lea TCB,a0 ; ^TerminalControlBlock
jsr (a2) ; Routine ausführen
bsr.s UPDATE_END ; Cursor wieder ein
rts ; fertig
\esc_sml:
subi.w #"a"-"A",d0 ; Offset für Kleinbuchstaben abziehen
bmi.s \esc_fail ; <"a", ignorieren
cmpi.w #"z"-"a",d0 ; >"z"?
bgt.s \esc_fail ; ja, ignorieren
bsr UPDATE_CRS ; Cursor ausschalten
lea SET_COLOR,a1 ; Adresse der 1. Routine
add.w d0,d0 ; Zeichen als Wort-Offset
lea E_SML,a2 ; AAdress-Tabelle
move.w 0(a2,d0.w),a2 ; Adress-Offset holen
adda.l a1,a2 ; + 1. Routine
lea TCB,a0 ; ^TerminalControlBlock
jsr (a2) ; Routine ausführen
bsr UPDATE_END ; Cursor einschalten
rts ; fertig