Extended VT52-Emulator Teil 1

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:

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.

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


Aus: ST-Computer 04 / 1988, Seite 122

Links

Copyright-Bestimmungen: siehe Über diese Seite