← ST-Computer 04 / 1988

Extended VT52-Emulator Teil 1

Grundlagen

Bild 1. Auch das Anzeigen und Übertragen (DFÜ) von Bildern in DOODLE-Format ist möglich.

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.

Features

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.

Bild 2: Umschalten von GEM-Fonts

Installation

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

Des’ Trap ich bieg’, des’ GEM ich sing’

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.

Bild 3: Verschiedene Schriftattribute

Traphandler

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:

  • 0x2 (CCONOUT): Ausgabe eines Zeichens; Steuerbefehle werden ausgewertet.
  • 0x6 (CRAWIO) mit Zusatzparameter <> $FF: wie CCONOUT.
  • 0x9 (PRINT LINE): Ausgabe eines mit Null abgeschlossenen Strings, wobei in selbigem enthaltene Steuerzeichen ausgewertet werden.
  • 0x40 (WRITE) mit Ausgabekanalnummer 1 (CON:): Ausgabe eines Strings, dessen Länge auf dem Stack übergeben werden muß; Steuerzeichen werden ausgewertet.

VDI:

  • 100 (Open Virtual Screen Workstation): Anmelden eines Terminals beim VDI-Server; zusätzlich zur Originalroutine wird der Cursor noch in Home-Position gefahren.
  • 101 (Close Virtual Screen Workstation): Abmelden des Bildschirms; zusätzlich zur Originalroutine wird der Cursor ausgeschaltet.
  • 3 (Clear Workstation): Bildschirm löschen
  • 5 (VDI Escape Sequences), Unterfunktionsnummern :
    • 1 bis 15 (allg. Ausgabe- und Steuerbefehle)
    • 101 (Set Line Offset): Festlegen des Abstandes zwischen log. Bildschirmanfang und 1. Textzeile in Pixel.
    • 102 (Init System Font): Installieren eines Zeichensatzes für normale Textausgaben (ohne Attribute). Diese Funktion beeinflußt NICHT die Textausgabe unter GEM (z.B. VDI 8 und damit letztendlich LINE A-Funktionen).

BIOS:

  • 3 (BCONOUT) Zeichenausgabe über die Kanalnummern:
    • 2 (CON:) Ausgabe mit Steuerzeichen
    • 5 (ASC:) Steuerzeichen werden nicht interpretiert, sondern als “normale” Zeichen gedruckt.
  • 12 (WRITE ASC) Ausgabe eines mit Null abgeschlossenen Strings, wobei darin enthaltene Steuerzeichen durch entsprechende Symbol ersetzt und nicht interpretiert werden.

XBIOS:

  • 21 (CURSCONF) Einstellen der Cursorattribute
  • 100 (INIT SCREEN) Rücksetzen der Bildschirmausgabe

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.

Vektor-Wirrwarr

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(0x00-0xFF) 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