Dieser Anhang beschreibt im Detail die Implementierung des ISDNLAP und seiner Funktionen. Besonderer Wert wird auf die Beschreibung der Installation des Link Access Protocols gelegt, da die Schnittstelle zum AppleTalk Protocol Stack in der vorhandenen Dokumentation zum LAP Manager nicht ausreichend behandelt wird.
Eine Implementierung eines ISDNLAP muß folgende Anforderungen erfüllen:
v Prozeduren zum Auf- und Abbau von Wählverbindungen nach dem X.21-Protokoll und zur automatischen Verbindungssteuerung müssen bereitgestellt werden.
v Eine dynamische Node-Adressierung und eine Funktion zur Ermittlung der Rufnummer zu einer Node ID sind zu integrieren.
v Ankommende Pakete müssen von einem Interrupt Handler entgegengenommen und anhand des Paketkopfes an den LAP Manager weitergereicht werden. Für das weitere Einlesen muß das ISDNLAP die Prozeduren ReadPacket, ReadRest des ALAP durch eigene Prozeduren ersetzen.
v Der LAPWrite-Code im ATalkHk2 muß durch eine neue Sendeprozedur substituiert werden, die sich nahtlos in den AppleTalk Protocol Stack einfügt.
Das ISDNLAP kennt grundsätzlich zwei Zustände: Den Ruhezustand, der auf die Übertragung eines Paketes oder einen ankommenden Anruf wartet, und die Paketübertragungsphase, in der Datenpakete ausgetauscht werden. Der Übergang vom einen in den anderen Zustand geschieht über die Initialisierungsprozeduren InitSCCStuff und InitHDLCStuff.
Bevor mit der Implementierung des ISDNLAP begonnen werden kann, ist ein geeignetes Entwicklungswerkzeug zu wählen. Auch über die Speichersituation bei der Implementierung muß man sich klar werden[1]. Wichtig für eine erfolgreiche Implementierung ist zudem die Wahl von geeigneten Testhilfen, wie Debugger und History.
Für den Macintosh gibt es eine Reihe guter Entwicklungsumgebungen. Favorisiert wurde schließlich der Macintosh Programmers Workshop (MPW) in der Version 2.02, das offizielle Entwicklungswerkzeug der Firma Apple. Seine Vorteile liegen vor allem in der Vollständigkeit und der Aktualität der Interfacefiles. MPW unterstützt verschiedene Programmiersprachen[2] und erlaubt ein einfaches Zusammenbinden von Moduln in unterschiedlichen Sprachen. Pascal- und Assembler-Prozeduren können damit auf einfache Weise mit dem MPW kombiniert werden.
Abb. B.1.1: Die MPW-Shell
Die Entwicklungsumgebung enthält alle Hilfsmittel die zur Erstellung umfangreicher Programme notwendig sind. Anderen Entwicklungspaketen voraus hat der MPW die hervorragende Unterstützung der Implementierung von „stand alone“-Code[3], der nicht zu einer Applikation gehört, sondern in das System eingebunden wird. Als größter Nachteil muß die Benutzeroberfläche (MPW-Shell) betrachtet werden (Abb. B.1.1). Der MPW ist in der Bedienung so kompliziert und in seinen Möglichkeiten so umfangreich, daß es einiger Übung bedarf, ein Programm zu übersetzen. Durch die Abarbeitung der Befehle in einer Makrosprache wird zudem die Verarbeitungsgeschwindigkeit sehr herabgesetzt. Bedauerlicherweise hat der Pascal-Compiler der vorliegenden Version einige latente Fehler. Beispielsweise versagt der Compiler beim Ermitteln von Feldadressen in gepackten Records, oder wenn gepackte Records über Handles angesprochen werden. Dennoch gibt es keine echte Alternative zum MPW.
Aus Gründen der besseren Wartbarkeit und Übersichtlichkeit wurde versucht, den Großteil des ISDNLAP in Pascal zu implementieren. Da die Schnittstelle zum LAP Manager „register based“ ist, werden Glueroutinen, die in Pascal entweder nicht implementiert werden können oder in Assembler einfacher zu realisieren sind, in M68000-Assembler codiert. Erhebliche Schwierigkeiten entstehen auch, wenn sich am Anfang eines Codes eine Sprungtabelle befindet. Die Erfahrung hat gezeigt, daß es oft mühsamer ist, Glueroutinen als Inline-Statements oder als „tricky Pascal code“ zu implementieren als direkt in Assembler. Zudem erzeugt jeder Compiler anderen Objektcode und belegt andere Register.
Das systemnahe Programmieren mit Pascal birgt einige Gefahren in sich. So sollte man vorsichtig mit der Arithmetik von Adressen sein, besonders dann, wenn innerhalb eines WITH-Statements ein Pointer oder Handle manipuliert wird. Durch das Dereferenzieren des WITH-Statements wird die Basisadresse eines Feldes in einem Register gehalten. Es nützt daher nichts, wenn man die Variable mit der Basisadresse direkt manipuliert. Eine weitere Gefahr, die durch die Optimierung des Compilers entsteht, ist das Pollen von globalen Variablen, die über Interrupts gesetzt werden. Dadurch, daß in einer Schleife nur eine Variable abgefragt wird, kann der Compiler die Variable in einem Register halten. Da eine Interruptroutine nur die globale Variable verändert, nicht aber das Register, in dem die Variable gehalten wird, terminiert die Schleife nicht.
In solchen Fällen hilft es, den vom Pascal-Compiler erzeugten Objektcode zu analysieren. Das ist nicht die Art, in der man Pascal programmieren sollte. In vielen Zweifelsfällen jedoch die einzige Möglichkeit zu verstehen, wie ein Compiler Code erzeugt. Oft genügt es mit {$W-} den Optimizer des Compilers abzuschalten.
Zu den notwendigen Programmierwerkzeugen gehören auch Test- und Debughilfen. In diesem Abschnitt wird daher der Einsatz von derartigen Hilfsmitteln bei der Implementierung von ISDNLAP beschrieben.
Trotz vorsichtiger Programmierung und sorgfältigem Durchdenken von Algorithmen kommt es gelegentlich zu Programmabstürzen oder zu Fehlverhalten von Programmen, wobei die Ursachen nicht offensichtlich sind. Um Fehler, wie Adreßfehler[4], „Illegal Instructions“[5] und Busfehler[6], zu lokalisieren und zu beseitigen, wird ein Debugger verwendet.
Abb. B.1.2: Die gebräuchlichsten Debugger
Bevorzugt wird dabei der TMON- oder MacsBug-Debugger eingesetzt (Abb. B.1.2). Beide sind keine Sourcecode-Debugger, sondern arbeiten auf Assembler-Ebene. Sie werden automatisch installiert, wenn sie sich beim Systemstart im System Folder befinden. Gerufen werden die Debugger in folgenden drei Fällen:
v Durch einen Debugger-Interrupt (Interruptpriorität 4–7), der beim Drücken des Debugger-Knopfes an der Seite des Macintosh ausgelöst wird.
v Von einem Programm aus über einen speziellen Debugger-Trap ($A9FF). In Pascal verwendet man entweder eine Inline-Prozedur oder eine Prozedur Debugger.
v Durch das Auftreten eines System-Fehlers. Am häufigsten handelt es sich dabei um Adreßfehler oder „Illegal Instructions“.
Beide Debugger bieten die Möglichkeit, den Speicher sowohl als „Hexdump“ als auch disassembliert zu betrachten. Während TMON durch veränderbare Fenster komfortabler in der Handhabung ist, bietet MacsBug wesentlich mehr Möglichkeiten. Für welchen Debugger man sich entscheidet hängt von der Situation ab.
Um den Programmfluß zu verfolgen, kann in einer normalen Applikation eine Ausgabe auf den Bildschirm erfolgen[7]. Bei der Implementierung des ISDNLAP ist das nicht so ohne weiteres möglich, da der Bildschirm meist von einer anderen Applikation beschrieben wird. Zudem benutzen die Ausgaberoutinen der Toolbox den Memory Manager und können daher nicht in Interrupts verwendet werden. In vielen Fällen ist eine Ausgabe auf den Bildschirm ohne Pufferung auch zu langsam, um bei kurz hintereinander ankommenden Paketen eine Ausgabe auf den Bildschirm zu ermöglichen.
Für die Implementierung des ISDNLAP wurde daher ein History verwendet, das von P. Schulthess zum Monitoren des PDEF10 entwickelt wurde[8]. Bei ISDNLAP kann das History jedoch nicht auf dem Application Heap des Macintosh angelegt werden, da dieser beim Beenden eines Applikationsprogrammes „gespült“ wird. Damit die Aufzeichnungen im History auch nach dem Beenden eines Programms erhalten bleiben, wird das History auf dem System Heap angelegt. Installiert wird das History bei der Initialisierung des ISDNLAP durch die Prozedur EavesInit. Da es unter den Pascal-Interfaces des OS keine ‘NewHandleClrSys’-Prozedur gibt, ist für das Anlegen des Histories ein kurzes Stück Assembler als Glueroutine notwendig (Abb. B.1.3).
NewHandleClrSys PROC EXPORT ; create handle with zeros
Move.L 4(A7),D0 ; get size
_NewHandle SYS,CLEAR ; create handle on sysheap
Move.L A0,8(A7) ; return handle
Move D0,MemErr ; result of trap
Move.L (A7)+,(A7) ; clear stack pointer
Rts ; go home
DC.B 'NEWHAND '
ENDP
Abb. B.1.3: Der Assembler-Glue zum Anlegen eines mit Nullen gefüllten Handles.
Beschrieben wird das History durch Aufruf der Prozeduren EavesHist und PtrEavesHist. Während EavesHist eine Marke und ein String als Meldung übergeben wird, erwartet PtrEavesHist die Länge einer Meldung und einen Zeiger darauf.
PROCEDURE EavesHist(mark: Str8; VAR str: Str255);
PROCEDURE PtrEavesHist(marke: Str8; Size: INTEGER; Ptr: charPtr);
Durch bedingte Compilierung kann bestimmt werden, welche Aktionen des ISDNLAP aufgezeichnet werden. Ist das Programm ausgetestet, so läßt sich das Tracen abschalten, wodurch der Code schneller und kleiner wird. Gesetzt werden die Trace-Optionen in der „Make-Datei“ (ISDNLAP.make) des ISDNLAP:
POptions = -d TDEBUG=TRUE -d RDEBUG=FALSE -d DDEBUG=TRUE
Mit der Dump-Funktion eines Debuggers kann das History betrachtet werden. Hierzu wird mit der Suchfunktion der Anfang des Histories, der durch die Marke „[ISDNInit]“ gekennzeichnet ist, lokalisiert und das Dumpfenster des TMON mit „(v)“ auf die gefundene Speicherposition gesetzt[9]. Einen Auszug aus dem History zeigt Abbildung B.1.4.
0002E4A6 5B49 5344 4E49 6E69 745D 3534 3100 0000 [ISDNInit]541•••
0002E4B6 5B49 6E69 7453 4343 5D00 0000 0000 0000 [InitSCC]•••••••
0002E4C6 5B54 5061 636B 6574 5D32 3535 0000 0000 [TPacket]255••••
0002E4D6 5B44 6F44 6961 6C5D 0000 0000 0000 0000 [DoDial]••••••••
0002E4E6 5B46 696E 645D 0000 0000 0000 0000 0000 [Find]••••••••••
0002E4F6 5B44 6961 6C55 705D 3534 3100 0000 0000 [DialUp]541•••••
0002E506 5B49 6E69 7453 4343 5D00 0000 0000 0000 [InitSCC]•••••••
0002E516 5B44 6961 6C54 4F31 5D16 1635 3431 2B00 [DialTO1]••541+•
0002E526 5B48 616E 6755 705D 0000 0000 0000 0000 [HangUp]••••••••
0002E536 5B49 6E69 7453 4343 5D00 0000 0000 0000 [InitSCC]•••••••
0002E546 5B54 5061 636B 6574 5D32 3535 0000 0000 [TPacket]255••••
0002E556 5B44 6F44 6961 6C5D 0000 0000 0000 0000 [DoDial]••••••••
0002E566 5B46 696E 645D 0000 0000 0000 0000 0000 [Find]••••••••••
usw.
Abb. B.1.4: Auszug aus dem History im Speicher
Bei der Deinstallation des ISDNLAP muß der Speicher, der durch das History belegt wird, wieder freigegeben werden. Das geschieht in der Prozedur RestoreOldLAP durch den Aufruf der Toolbox-Prozedur DisposHandle.
Für die Entwicklung von AppleTalk-Applikationen steht eine Vielzahl von Tools zur Verfügung. Hier sollen diejenigen kurz beschreiben werden, die bei der Entwicklung des ISDNLAP von Nutzen waren und frei erhältlich sind. Es gibt viele neuere Hilfsmittel, die aber meist sehr teuer sind und deren Nutzen nicht bewiesen ist. Wo diese Tools nicht ausreichten, wurden eigene kleine Hilfsprogramme entwickelt.
Abb. B.1.5: Hilfsmittel zur Entwicklung von Netzwerkapplikationen
v Peek: Hier handelt es sich um ein Monitor-Programm, das alle Pakete, die über eine AppleTalk-Leitung wandern, anzeigt und aufzeichnet. Wahlweise können auch Kontrollpakete des LAP betrachtet werden. Ein Abspeichern von Aufzeichnungen ist ebenfalls möglich.
v Poke: Das AppleTalk-Poke-Programm erlaubt es, bis zu zehn LAP-, DDP- und ATP-Pakete zu definieren und wiederholt auf das Netzwerk zu senden. In einer Dialogbox werden die Parameter für jedes Paket definiert.
v NetCheck: Dieses Programm sucht nach Einträgen auf dem Netzwerk. Dabei ist es möglich, zwischen benannten und unbenannten Einträgen zu unterscheiden. Leider zeigt es keine Zonennamen an.
v ATITools: Das ist eine Eigenentwicklung, welche benannte Einträge auf dem Netzwerk registrieren, suchen und wieder löschen kann. Zusätzlich besteht mit diesem Programm die Möglichkeit zu Testzwecken die Node ID eines Knotens zu verändern.
ISDNLAP ist ein ADEV-File und als solches keine Applikation, sondern „stand alone“-Code. Somit sind bei der Belegung des Speichers einige Besonderheiten zu beachten.
Die Datenstrukturen des AppleTalk im Speicher sind stark zerklüftet (vgl. Abb. B.1.6). Manche Datenblöcke sind über globale Variablen erreichbar[10]. Ein großer Teil ist jedoch undokumentiert und sollte daher auch nicht verwendet werden. Jeder Treiber besitzt lokale Variablen und Datenblöcke. Für die Implementierung eines alternativen Link Access Protocols sind vor allem die lokalen Variablen des .MPP von Bedeutung.
Abb. B.1.6: Die Verkettung der Datenstrukturen des AppleTalk
Diese lokalen Variablen des .MPP-Treibers sind in der Struktur der .MPP Local Variables abgelegt. Ihre Position wird in der globalen Systemvariablen ABusVars mit der Adresse $2D8 festgehalten. Der Speicher für einen Names Table-Eintrag muß von der jeweiligen Applikation, die den Namen hinzufügt, gestellt werden. Die Applikation ist auch für das Aufräumen nicht mehr benötigter Einträge verantwortlich.
sysLAPAddr EQU $0 ; This node's LAP address
toRHA EQU $1 ; Top of RHA ($18 Bytes )
sysABridge EQU $19 ; Node address of a bridge [byte]
sysNetNum EQU $1A ; This node's network number [word]
vSCCEnable EQU $1C ; SR to reenable SCC interrupts [word]
atpVars EQU $1E ; ATP variable ptr (high byte is flag)
afterGlobals EQU $22 ; After the globals
NBPC1 EQU $8A ; Pointer to NBP Code 1 ( undocumented )
NBPC2 EQU $8E ; Pointer to NBP Code 2 ( undocumented )
Names Table EQU $90 ; Pointer to first Names Table Entry
Abb. B.1.7: Bekannte Offsets innerhalb der lokalen .MPP-Variablen
Leider ist über die Struktur der lokalen .MPP-Variablen sehr wenig bekannt. In den Assembler-Equates zum MPW[11] befinden sich einige wichtige Offsets (siehe Abb. B.1.7). Ferner ist mir durch Debugging die Position des Zeigers auf die Names Tables bekannt ($90). Bei Macintosh II hat sich diese jedoch bereits geändert.
Temporärer Speicher wird in Pascal-Programmen auf dem Stack angelegt und nach der Beendigung der Prozedur wieder freigegeben. Globale Variablen, die für die Dauer der Aktivierung des ISDNLAP benötigt werden, bereiten jedoch Schwierigkeiten. Da es sich bei ISDNLAP um „stand alone“-Code handelt, können globale Variablen vom Pascal-Compiler nicht relativ zum A5-Register adressiert werden (vgl. Abb. B.1.8).
Abb. B.1.8: Das Memory Mapping im Macintosh
Für ISDNLAP gibt es zwei Möglichkeiten globale Variablen anzulegen:
v Zuerst kann beim Öffnen des Treibers ein Handle von der Größe der GlobalVars auf dem Heap reserviert werden. Da das ISDNLAP über mehrere Applikationen hinweg aktiv ist, muß dies auf dem System Heap geschehen. Die übliche Pascal-Prozedur NewHandle kann hierzu nicht verwendet werden, da diese ein Handle nur auf dem Application Heap anlegt[12].
v Schließlich besteht die Möglichkeit zum ‘atlk’-Code des ISDNLAP einen ausreichend großen Datenblock hinzuzulinken und die globalen Variablen dort abzulegen.
GLOBALS PROC
DC.B '**************************************************'
◊Ç◊◊◊◊◊◊◊◊◊◊◊◊◊◊◊◊◊◊
DC.B '**************************************************'
Vom Assembler-Modul wird der Bezeichner als Prozedur exportiert. Das Pascal-Modul importiert diese Prozedur und greift über die Adresse auf die globalen Variablen zu:
PROCEDURE Globals; EXTERNAL;
MyGlobals:=GlobalPtr(@Globals);
WITH MyGlobals^ DO …
Ein fester Block von globalen Variablen hat zwei Vorteile: Zum einen erübrigt sich beim Debuggen die Suche nach den Variablen im Heap. Zum anderen ist das Anlegen eines Handles im System Heap von Pascal aus nicht möglich; es muß eine Hilfsprozedur in Assembler implementiert werden.
Da sämtliche Prozeduren des ALAP machen nach der Initialisierung keinen Gebrauch mehr vom Memory Manager, da das Reservieren von Speicher durch das LAP mit Hilfe des Memory Managers Probleme bereitet. Diese treten dann auf, wenn ein Klient des LAP ein Handle dereferenziert und mit dem Pointer arbeitet und dabei das Handle nicht „gelockt“ hat. Durch einen Interrupt, der eine Prozedur des LAP ruft, die den Memory Manager verwendet, könnten so Speicherblöcke verschoben und die Pointer des unterbrochenen Programmes korrumpiert werden. Daher muß in den Interruptroutinen des ISDNLAP gänzlich auf die Verwendung von Toolbox-Routinen, die Speicher verschieben oder löschen, verzichtet werden[13].
Die ‘atlk’-Resource des ISDNLAP wird, wie alle AppleTalk-Treiber, in den System Heap geladen. Dort herrscht leider bei älteren System-Versionen[14] (< 6.0) akuter Platzmangel. So kann beim Starten des Systems das ISDNLAP nicht geladen werden, da zu diesem Zeitpunkt der Speicher besonders knapp ist. Der einzig mögliche Ausweg ist die Vergrößerung des System Heaps. Eine Aufteilung des Codes ist ebenso nicht sinnvoll, da auch ein eventuell gesonderter Treiber im System Heap Platz finden muß. Außer beim Macintosh 128 wird auch der NBP- und ATP-Code in den System Heap geladen.
Um das ISDNLAP in den System Heap zu laden, muß dieser vergrößert werden. Die Größe des System Heaps wird durch einen Eintrag in den Bootblocks des Macintosh bestimmt. Falls der Eintrag in den Bootblocks nicht ausreicht, setzt zusätzlich ein INIT die Größe auf einen Minimalwert, um die aktuelle System-Version zu unterstützen. Die genaue Position der System Heap-Größe in den Bootblocks ist $86–$89. In jeder System Resource befindet sich eine Kopie der Bootblocks. Diese kann mit dem ResEdit in die Resource Fork des FEdit kopiert und mit der Update-Funktion des FEdit auf die Bootblocks der Diskette geschrieben werden. Somit lassen sich Modifikationen leicht rückgängig machen.
Globale Variablen werden zusammen mit dem History und dem LAP-Code im System Heap untergebracht. Auch wenn bei neueren System-Versionen dort kein Platzmangel herrscht, muß dennoch darauf geachtet werden, daß der Code des ISDNLAP nicht zu umfangreich wird.
Kommunikationsprotokolle arbeiten mit Zeitlimits um Verklemmungssituationen zu vermeiden. Das Betriebssystem des Macintosh offeriert eine sehr elegante Methode, um Timeouts zu realisieren. Über den Time Manager kann eine Task in die Time Manager Queue eingehängt werden, die nach einer vorgegebenen Anzahl von Millisekunden genau einmal gerufen wird und den Timeout signalisiert. Diese Methode wurde in DialUp verwendet (TimeoutTask). Leider kann eine derartige Task in einer Interruptroutine nicht als Zeitbegrenzung fungieren, da während eines Interrupts Tasks ausgeschlossen sind. Auch Ticks können nicht verwendet werden, da der Zähler in der globalen Variable Ticks in Interrupts nicht erhöht wird. Es bleibt nur die Möglichkeit, Zählschleifen zu verwenden. Aber auch das ist nicht problemlos, da das LAP unabhängig vom Rechnertyp und dessen Verarbeitungsgeschwindigkeit sein soll. Eine globale Variable TimeDBRA, die angibt wieviele Decrement-and-Branch-Schleifen in einer Millisekunde ausgeführt werden können, ist am Macintosh Plus nicht verfügbar und für Pascal-Schleifen zu ungenau.
Das ISDNLAP erscheint auf dem Desktop als System-Datei vom Typ ADEV. Dieses ADEV enthält in seiner Resource Fork die Teile des ISDNLAP als einzelne Resourcen (Abb. B.2.1).
Abb. B.2.1: Die Struktur des ADEV ISDNLAP
Im wesentlichen lassen sich dabei die Resourcen in drei Gruppen unterteilen:
v Die ‘adev’-Resource, die die Wechselwirkungen mit dem CDEV Netzwerk bei der Auswahl übernimmt.
v Die ‘atlk’-Resource, die den eigentlichen Code des ISDNLAP enthält.
v Einige weitere Resourcen, die für den Dialog mit dem Benutzer und dem Betriebssystem zuständig sind.
Die ‘adev’-Resource enthält den Code der Prozeduren, die bei der Auswahl des LAP im Kontrollfeld gerufen werden[15]. Der Code der ‘adev’-Resource wurde aufgrund seiner Systemnähe vollständig in Assembler implementiert und besteht im wesentlichen aus einer Sprungtabelle, den Prozeduren GetADEV und SelectADEV sowie der Zeichenkette ‘ISDNLAP’, die unter dem ISDNLAP-Icon im Kontrollfeld erscheint.
Da der Code der ‘adev’-Resource nur zur Auswahl benötigt wird und danach nicht mehr, kann er anschließend wieder aus dem Speicher entfernt werden. Da es sich um Code handelt, darf die Resource allerdings nicht verschoben werden und muß „locked“ sein. Folgende Anweisung veranlaßt den Resource-Compiler den ‘adev’-Code in die Resource des ADEV ISDNLAP einzubinden :
include "ISDNadev" 'adev' (126) AS 'adev' (126, locked);
Der eigentliche Code des ISDNLAP ist in der ‘atlk’-Resource des ADEV enthalten. Er besteht aus einer Sprungtabelle, den Installationsprozeduren für den LAPWrite-Code, dem Assembler-Glue zum Aufruf der Sende- und Empfangsprozeduren in Pascal und dem eigentlichen ISDNLAP-Code in Pascal.
Die Assembler-Prozeduren doAInstall und doAShutDown dienen dabei der Installation. Der Glue für die Pascal-Routinen besteht aus den Prozeduren LAPWrite, ReadDispatch, ReadPacket und ReadRest. Schließlich enthält die ‘atlk’-Resource noch eine Glueroutine NewHandleClrSys, die ein Handle auf dem System Heap anlegt, was in Pascal nicht möglich ist. Am Ende des Assembler-Codes der ‘atlk’-Resource wird ein Datenblock angelegt, der die globalen Variablen des Pascal-Moduls aufnimmt. Der Pascal-Code des ISDNLAP, der eine Vielzahl von Prozeduren enthält, wird zu diesen Assembler-Routinen hinzugebunden (vgl. Abb. B.2.1).
Da der Code der ‘atlk’-Resource über mehrere Applikationen hinweg aktiv ist, muß er im System Heap untergebracht und „festgeschraubt“ werden[16]. Dazu ist jeweils ein Bit in der Resource zu setzen:
include "ISDNatlk" 'atlk' (126) AS 'atlk' (126, sysheap,locked);
Neben den Code-Resourcen befinden sich noch weitere Resourcen in der Resource Fork des ADEV (vgl. Abb. B.2.2). Die BNDL-, FREF- und ICN#-Resourcen dienen dem Anzeigen des ISDNLAP-Icons auf dem Desktop. Dieses ist identisch mit dem Icon im Kontrollfeld.
Resource-Typ |
Resource ID |
‘BNDL’ |
-4032 |
‘FREF’ |
-4032 |
‘ICN#’ |
-4032 |
‘STR ’ |
-4032 |
‘DLOG’ |
128 |
‘DITL’ |
128 |
‘adev’ |
126 |
‘atlk’ |
126 |
Abb. B.2.2: Die Resourcen des ADEV ISDNLAP
In einer ‘STR ’-Resource ist die Rufnummer des NameServers abgelegt. Sie wird bei der Auswahl des ISDNLAP vom Benutzer über eine Dialogbox, die sich in der ‘DLOG’- und ‘DITL’-Resource befindet, gesetzt. Ferner ist zu beachten, daß die Resource ID -4032 für die Typen ‘BNDL’, ‘FREF’, ‘ICN#’ und ‘STR ’ für alle ADEVs fest ist und nicht verändert werden darf. Die ID der ‘atlk’- und ‘adev’-Resourcen muß im Bereich von 1–254 liegen, die Nummern der zusätzlichen Resourcen sind dagegen frei wählbar.
Die Prozeduren des ISDNLAP lassen sich nach ihren Aufgaben grob in fünf Bereiche gliedern (Abb. B.2.3): Die Auswahlprozeduren für das Kontrollfeld, die Glueroutinen zu AppleTalk, die Initialisierungs- und Installationsprozeduren des ISDNLAP, die Verbindungssteuerung und die Paketübertragung.
Abb. B.2.3: Das Zusammenwirken der Prozeduren des ISDNLAP
v Die Auswahlprozeduren im ‘adev’-Code sind vollständig in Assembler geschrieben und übernehmen beim Anzeigen und bei der Auswahl des LAP im Kontrollfeld den Dialog mit dem CDEV Netzwerk und dem Benutzer.
v Die Installations- und Initialisierungsprozeduren in ‘atlk’-Code übergeben bei der Auswahl die Adresse des LAPWrite-Codes an den LAP Manager und Initialisieren das ISDNLAP.
v Die Assembler-Glueroutinen bilden das Interface zwischen den Pascal-Routinen des ISDNLAP und den Routinen des LAP Managers und des Betriebssystems, die ihre Parameter in Registern übergeben.
v Die Pascal-Prozeduren der Verbindungssteuerung übernehmen den Verbindungsauf- und -abbau, die dynamische Node-Adressierung sowie die Suche nach einer Rufnummer am NameServer.
v Die Pascal-Prozeduren zur Paketübertragung sind für das Senden und Empfangen von Paketen verantwortlich.
v Verschiedene kleinere Hilfsprozeduren in Pascal unterstützen die Übertragungs- und Verbindungssteuerungsprozeduren.
Die Prozeduren des ISDNLAP sind in drei Units zusammengefaßt. Dabei handelt es sich um zwei Assembler- und ein Pascal-Modul. Im einzelnen besteht das ISDNLAP aus folgenden Quelldateien:
Abb. B.2.4: Die Komponenten des ISDNLAP-Quellcodes
ISDNLAP.p |
enthält den eigentlichen LAP-Code. Hier befinden sich die Prozeduren zur Verbindungssteuerung und zur Paketübertragung. Die Reihenfolge der Prozeduren in ISDNLAP.p ist so gewählt, daß wenig FORWARD-Deklarationen notwendig werden. |
ISDNatlk.a |
integriert die Routinen zur Installation und Deinstallation des LAPWrite-Codes im ATalkHk2 und einige Glueroutinen zum Aufruf der Pascal-Prozeduren aus ISDNLAP.p. |
ISDNadev.a |
besitzt zwei Routinen zur Auswahl des ISDNLAP im Kontrollfeld. |
ISDNLAP.r |
enthält die Resourcen des ADEV ISDNLAP. |
ISDNLAP.make |
übersetzt und bindet das ISDNLAP nach den vorgegebenen Build-Kommandos. |
LAPMgrEqu.a |
stellt Konstanten zur Verfügung, die von der ISDNadev.a- und ISDNatlk.a-Unit benötigt werden. |
Die Assembler- und Pascal-Moduln verwenden Toolbox- und OS-Routinen, die jeweils importiert und vom Linker hinzugebunden werden müssen. Um zu verhindern, daß Code zur Bereichsüberprüfung eingebunden wird, muß mit der Compiler-Option {$R-} das Range Checking des Compilers deaktiviert werden. Damit auch die importierten Moduln mit dieser Option übersetzt werden, muß das vor USES geschehen:
{$R-} USES {$LOAD $$Shell(Plibraries)MemQDOSToolPack}
Memtypes, Quickdraw, OSintf, Toolintf, Packintf, PasLibIntf;
Die einzelnen Teile des ISDNLAP werden durch den Linker schließlich zu den ‘adev’- und ‘atlk’-Resourcen zusammengebunden. Zur Erzeugung der ‘adev’-Resource wird die ‘adev’-Assemblerunit mit der SetNumber-Prozedur aus ISDNLAP.p kombiniert. Aus den restlichen Pascal-Prozeduren in ISDNLAP.p und dem ‘atlk’-Assemblermodul entsteht durch Linken die ‘atlk’-Resource:
ISDNadev ƒƒ ISDNadev.a.o ISDNLAP.p.o
ISDNatlk ƒƒ ISDNatlk.a.o ISDNLAP.p.o
Schließlich erzeugt der Resource Compiler (Rez) aus den einzelnen Code-Resourcen das ADEV ISDNLAP und Setfile setzt das Bundlebit, den Typ und den Creator:
ISDNLAP ƒƒ ISDNLAP.r ISDNadev ISDNatlk
Rez ISDNLAP.r -o ISDNLAP
Setfile -a B -t 'adev' -c 'ISDN' ISDNLAP
duplicate -y ISDNLAP “{SystemFolder}”ISDNLAP
Zuletzt wird mit dem Kommando „duplicate“ eine Kopie des ISDNLAP im System Folder abgelegt. Somit ist es sofort für das Kontrollfeld und das CDEV Netzwerk verfügbar.
Die Konstanten des ISDNLAP, wie die Länge von Timeouts, bestimmte Kontrollzeichen und Strings[17], werden zur Zeit noch im Quelltext (hard coded) untergebracht. Das spart vor allem globale Variablen und/oder Zeit für das Lesen von Resourcen aus der Resource Fork. Dadurch können jedoch Konstanten nur durch Änderung im Quelltext modifiziert werden. Für eine endgültige Version des ISDNLAP empfiehlt es sich daher darüber nachzudenken, welche Konstanten modifizierbar sein und somit als Resourcen angelegt werden müssen. Zusätzlich werden verschiedene Konstanten aus den Toolbox- und OS-Interfacedateien verwendet.
Die Verwendung von strukturierten Datentypen, wie Pascal sie anbietet, erlaubt einen höheren Abstraktionsgrad als die Assembler-Ebene und erleichtert damit den Zugriff auf Daten wesentlich. Die Objekte der Datenkommunikation lassen sich durch Pascal-Typen strukturieren und gewinnen somit gegenüber einem unstrukturierten Bitstrom an Anschaulichkeit .
Die wichtigsten Typen, die bei der Implementierung des ISDNLAP Verwendung finden, werden hier kurz erläutert[18]:
Port |
Ein Aufzählungstyp, dessen Konstanten die beiden Schnittstellen des Macintosh darstellen (MODEM, PRINTER). |
LineStatus |
Dieser Aufzählungstyp spiegelt die Zustände wider, die eine Verbindung annehmen kann. |
TransmitStatus |
Dieser Ergebnistyp gibt die Situationen, die bei der Übertragung eines Paketes auftreten können, wieder. |
FrameStatus |
Wird von den Empfangsprozeduren verwendet, um anzuzeigen, ob ein Paket fehlerfrei empfangen wurde oder welcher Fehler aufgetreten ist. |
IdStatus |
Die Zustände der Node ID bei der dynam. Node-Adressierung. |
aDataField |
Hier handelt es sich um das Format eines unstrukturierten Frames, d.h. ein Frame wird nur als ein Feld von Zeichen betrachtet. |
NumberCache |
In dieser Struktur wird die zuletzt gewählte Rufnummer mit zugehöriger Node ID abgelegt. |
ABusVars |
Die Struktur der lokalen .MPP-Variablen, soweit bekannt. |
SCCReg |
Über diesen Record werden die Kontroll- und Daten-Register des SCC adressiert. |
Level2IntTable |
Diese Struktur besitzt die Level-2 Interrupt Vector Table. |
MyWDSEntry |
Eine Kurzversion der Write Data Structure, in der Kontrollpakete für die TransmitFrame-Prozedur verpackt werden. |
aFrame
|
Dieser Variantenrecord spiegelt die Struktur eines LAP-Frames wider. Zum einen kann über ein Feld auf die Zeichen zugegriffen werden, zum anderen ist der Packet Header direkt ansprechbar. Hier ist auch die Struktur der Read Header Area enthalten. |
GlobalVars |
Ein Record, der den im Assembler-Quellcode angelegten Bereich der globalen Variablen strukturiert. |
Die globalen Variablen des ISDNLAP sind als Datenblock im Assembler-Modul ISDNatlk.a angelegt. Sie enthalten nur die notwendigsten Werte, die für die Dauer der Installation für verschiedene Prozeduren relevant sind. Die Struktur der globalen Variablen wird durch den Typ GlobalVars festgelegt. Die Felder dieses Records haben folgende Bedeutung:
MPPVars |
ABusVarsPtr |
Ein Zeiger auf die lokalen .MPP-Variablen. |
Guard |
BOOLEAN |
Dieses Flag verhindert asynchrone Aufrufe von TransmitPacket und signalisiert, daß ein Paket gesendet wird. |
SCCWrCtl, SCCRdCtl, SCCWrData, SCCRdData |
Ptr |
Über diese Adressen können die Write-, und Read-Kontroll- und Datenregister des SCC erreicht werden. |
NameServer-Number |
ISDNNumber |
Hier wird die Nummer des NameServers aus der Resource abgelegt. |
ActualNumber |
ISDNNumber |
Die ermittelte Rufnummer des Knotens, zu dem eine Verbindung aufgebaut werden soll. |
IsUp |
LineStatus |
Der Status einer Verbindung. |
Last Transmission |
LONGINT |
Die Anzahl der VBL-Task-Aufrufe seit dem zuletzt übertragenen Paket. |
TOut |
BOOLEAN |
Signalisiert einen Timeout durch TimeOutTask. |
VBLEntry |
VBLTask |
Eintrag in der VBLQueue[19] für die VBL-Task Terminator (Abbau einer Verbindung). |
MSecQueue |
TMTask |
Eintrag der Time Manager-Task TimeOutTask in der Time Manager Queue. |
AddrStatus |
IdStatus |
Diese Variable zeigt an, ob die Node ID einer Station gültig ist. |
HisAddress |
BYTE |
Die Node ID der Gegenstation der aktuellen Verbindung. |
OldReceive InterruptVector,OldReceive SpecialVector |
Ptr |
Hier werden die Interruptvektoren gespeichert, die sich vor der Installation des ISDNLAP in der Level-2 Interrupt Vector Table befanden. |
Cache |
NumberCache |
Im Cache befinden sich die Rufnummer und die Node ID derjenigen Station, zu der zuletzt eine Verbindung bestand. |
globzeux |
globrec |
Enthält einen Zeiger auf das History im System Heap. |
Der Zugriff auf den SCC des Macintosh ist „memory mapped“, d.h. er erfolgt über logische Adressen. Den 8 Kontrollregistern des SCC sind 8 Adressen im Adreßbereich zugeordnet. Für jeden Kanal (Port) gibt es sowohl für Lese- als auch für Schreibzugriffe jeweils ein Daten- und ein Kontrollregister. Die Basisadressen dieser Register haben folgende Werte:
Globale Variable |
Macintosh Plus |
Macintosh II[20] |
SCCRd ($1D8) |
$9FFF8 |
$50F04000 |
SCCWr ($1DC) |
$BFFF9 |
$50F04000 |
Abb. B.3.1: Die Basisadressen des SCC bei verschiedenen Macintosh-Modellen
Diese Werte können den globalen Variablen SCCRd und SCCWr des Macintosh-Betriebssystems entnommen werden. Da sich die Basisadressen mit neuen Betriebssystemversionen ändern können, empfiehlt es sich, diese stets den globalen Variablen zu entnehmen.
Abb. B.3.2: Adressierung der Daten- und Kontrollregister des SCC
Die Adressen der einzelnen SCC-Register werden aus den 2 Basisadressen SCCRBase und SCCWBase und den Offsets der Felder des folgenden Records gebildet[21]:
SCCReg = RECORD
bCtl: INTEGER;
aCtl: INTEGER;
bData: INTEGER;
aData: INTEGER
END;
Abb. B.3.3: Die Anordnung der SCC-Register als Pascal-Record
In Pascal erhält man die Adressen der Daten- und Kontrollregister eines Ports, indem man die Adresse von SCCWr bzw. SCCRd als Handle auf die Register des SCC interpretiert und dieses Handle entsprechend dereferenziert.
SCCWBasePtr:=SCCHandle($1DC);
SCCWrCtl:=@SCCWBasePtr^^.bCtl;
Über die Adressen der jeweiligen Register erfolgt dann der Schreib- und Lesezugriff auf die gewünschten Register:
SCCWrCtl^ := value; value := SCCRdCtl^;
Über die Datenregister erfolgt der Austausch von Nutzdaten. Auf sie kann direkt zugegriffen werden, da für beide Ports jeweils sowohl zum Schreiben als auch zum Lesen ein eigenes Datenregister zur Verfügung steht. Für die Programmierung des SCC verfügt jeder Kanal über einen internen Satz von Write- und Read-Registern, der nicht explizit auf logische Adressen abgebildet ist, sondern auf den über die Kontrollregister zugegriffen wird. Mit den Write-Registern wird der SCC initialisiert und gesteuert, die Read-Register dienen der Statusabfrage. Die Write-Register können nicht gelesen werden, so daß es leider nicht möglich ist, die programmierten Parameter zu überprüfen. Der Zugriff auf die internen Register geschieht in 2 Stufen:
Schreiben eines Kontrollregisters:
1. Die Nummer des Write-Registers, auf das zugegriffen werden soll, wird in das Write-Kontrollregister geschrieben.
2. Der Wert, der geschrieben werden soll, wird im Write-Kontrollregister abgelegt.
Im ISDNLAP wird das Beschreiben von Write-Kontrollregistern, wie es z.B. bei der Initialisierung des SCC notwendig ist, von der Prozedur Write_SCC übernommen:
PROCEDURE Write_SCC(reg, value: BYTE);
VAR MyGlobals: GlobalPtr;
BEGIN
MyGlobals:=GlobalPtr(@Globals);
WITH MyGlobals^ DO BEGIN
IF reg<>0 THEN SCCWrCtl^:=reg;
SCCWrCtl^:=value;
END
END; { Write_SCC }
Ähnlich wie der Schreibzugriff verläuft auch das Lesen eines Read-Kontrollregisters. Da meist das Read-Register 0 gelesen wird, ist im ISDNLAP keine gesonderte Read_SCC Prozedur vorgesehen.
Lesen eines Kontrollregisters:
1. Die Nummer des zu lesenden Read-Kontrollregisters wird in das Write-Kontrollregister geschrieben.
2. Der Wert des Read-Registers wird aus dem Read-Kontrollregister gelesen.
Eine Ausnahme bilden die Write- und Read-Kontrollregister 0. Auf sie kann in einem Schritt zugegriffen werden, indem bei einem Schreibzugriff ein Wert direkt in das Kontrollregister geschrieben und bei einem Lesezugriff das Kontrollregister direkt gelesen wird. Daß Register 0 gelesen werden soll, erkennt der SCC bei Lesezugriffen daran, daß vorher kein anderes Register gesetzt wurde. Bei Schreibzugriffen kann der SCC zwischen einer Registernummer und einem Wert für Register 0 unterscheiden, da die möglichen Registernummern und die möglichen Kommandos für Write-Register 0 disjunkt sind.
Die Beschreibung der einzelnen Register ist dem technischen Handbuch des SCC zu entnehmen[22]. Im folgenden werden wegen ihrer Bedeutung die Read-Register 0 und 1, die wichtige Statusregister für das Senden und Empfangen von Frames darstellen, sowie das Write-Register 0 beschrieben, über das Kommandos an den SCC gerichtet werden.
Abb. B.3.4: Die Read-Register 0 und 1
Das Read-Register 0 enthält u.a. den Status des Sende- und Empfangspuffers (FIFO) (vgl. Abb. B.3.4). Für die Implementierung der Verbindungssteuerung und einer Paketübertragung im HDLC-Mode sind lediglich die folgenden Bits von Bedeutung:
RxCharAvailable |
zeigt an, daß mindestens ein Zeichen im Empfangspuffer vorhanden ist. |
TxBufferEmpty |
Sobald dieses Bit gesetzt ist, ist das letzte Zeichen übertragen und ein weiteres Zeichen kann in den Sendepuffer geschrieben werden. |
TxUnderrun/EOM |
meldet, daß der SCC einen Frame mit der Prüfsumme und einem Flag abgeschlossen hat. Das Senden eines Frames ist beendet, sobald dieses Bit gesetzt ist. Es kann über das Write-Register 0 zurückgesetzt werden. |
Das Read-Register 1 gibt den Status eines empfangenen Zeichens an:
RxOverrunError |
signalisiert einen Überlauf des Empfangspuffers. Zu diesem Fehler kommt es, wenn ankommende Zeichen nicht schnell genug aus dem Empfangspuffer gelesen werden. Das Bit bleibt bei allen nachfolgenden Zeichen solange gesetzt, bis es durch Setzen des ErrorReset-Bits im Write-Register 0 zurückgesetzt wird. |
EndOfFrame |
Sobald der SCC das schließende Flag eines Frames erkennt, wird beim zuletzt empfangenen Zeichen das EOF-Bit gesetzt. |
CRCError |
gibt beim letzten Zeichen eines Frames an, ob die Prüfsumme des Frames korrekt ist. Da es den momentanen Zustand des CRC-Checkers widerspiegelt, hat es bei Zeichen innerhalb eines Frames keine Bedeutung und ist nur gültig, wenn gleichzeitig das EOF-Bit gesetzt ist. |
Neben den genannten Read-Register 0 und 1 ist vor allem das Write-Register 0 von Bedeutung (Abb. B.3.5). Über dieses Register kann, wie oben beschrieben, ein anderes Register ausgewählt werden. Darüber hinaus werden die Bits 3 bis 7 genutzt, um einige besonders häufige Kommandos auszuführen[23]:
Reset HIUS |
(00111000) |
Nimmt den höchsten Interrupt wieder zurück. |
Error Reset |
(00110000) |
Löscht die Fehlerbits in Read-Register 1. |
Send Abort |
(00011000) |
Sendet eine Abbruchsequenz. |
Reset EOM |
(11000000) |
Reset des End of Message-Bits. |
Abb. B.3.5: Das Write-Register 0
Ankommende Zeichen werden aus einem FIFO-Puffer gelesen. Dabei ist jedem Zeichen in diesem 3 Byte großen Empfangspuffer ein Status im internen Statuspuffer zugeordnet. Parallel zu jedem Zeichen wird der Status gepuffert, der über das Read-Register 1 gelesen wird (vgl. Abb. 6.3.1). Der über das Register 1 zurückgelieferte Status bezieht sich stets auf das nächste aus dem Puffer zu entnehmende Zeichen. Da mit dem Lesen eines Zeichens auch der dazugehörige Status aus dem Statuspuffer entfernt wird, muß zuvor der Status des Zeichens betrachtet werden. Erst danach darf das Zeichen selbst abgeholt werden.
Wird der LAPWrite-Code des ISDNLAP beim Öffnen des .MPP erstmals gerufen, um ein ENQ-Paket zu übertragen, so aktiviert er über die Prozedur InitSCCStuff den SCC für den Verbindungsaufbau. Vor dem ersten Senden eines Paketes, d.h. vor dem Öffnen des .MPP, darf InitSCCStuff nicht gerufen werden, da andernfalls Anrufe und Pakete angenommen werden könnten, ohne daß die lokalen Variablen des .MPP (ABusVars) initialisiert sind.
PROCEDURE InitSCCStuff(First: BOOLEAN);
Über den Parameter First wird der Prozedur InitSCCStuff mitgeteilt, daß sie erstmals gerufen wird. Das ist aus zwei Gründen erforderlich:
v Zum einen bestimmt die Prozedur, wie unter B.3.1.1 beschrieben, die Basisadressen der SCC-Register, was aber nur einmal geschehen muß.
v Zum anderen darf das Retten der alten Interruptserviceroutine (ISR) nur beim ersten Aufruf geschehen. Andernfalls werden sie überschrieben.
Nachdem InitSCCStuff die Basisadressen der SCC-Register für den gewünschten Port bestimmt hat, wird der SCC einmal „angelesen“ und so das Write-Kontrollregister zurückgesetzt. Dadurch wird verhindert, daß der SCC einen bereits in Write-Kontrollregister 0 befindlichen Wert irrtümlicherweise als Registerauswahl interpretiert.
Nun wird über die Prozedur Write_SCC ein Reset des ausgewählten Ports veranlaßt und der SCC für die Übertragung von Zeichen im BSC-Mode initialisiert. Das geschieht nach folgender Initialisierungssequenz:
Register |
Wert |
Bedeutung der Werte |
9 |
$40/$80 |
Reset von Port B oder Port A. |
4 |
$1 |
Synchroner Modus, 1 SYNC-Zeichen und ungerade Parität. |
10 |
$0 |
NRZ-Kodierung der Zeichen. |
6 |
$FF |
Sende binäre Einsen, falls Leitung idle. |
7 |
$16 |
(SYNC) SYN-Zeichen (00010110). |
11 |
$28 |
Externer Receive- und Transmit-Takt. |
14 |
$0 |
DTR zulassen. |
5 |
$2A |
7-Bit-Zeichen senden, Transmitter an, RTS-Signal an, high (Control OFF). |
3 |
$41 |
7-Bit-Zeichen empfangen, Receiver an. |
15 |
$8 |
DCD-Interrupts für die Maus zulassen. |
1 |
$9 |
Receive-Interrupts beim ersten Zeichen und Special Condition, Externe Interrupts für die Maus zulassen. |
9 |
$A |
Keinen Interruptvektor ausgeben und Master Interrupts zulassen. |
Abb. B.3.6: Initialisierungssequenz des SCC für den Verbindungsaufbau
Anmerkungen zur Initialisierungssequenz
der Verbindungssteuerung:
v Nach dem Reset eines Ports sind verschiedene Kontrollregister bereits passend vorbelegt und müssen daher nicht mehr initialisiert werden.
v Das RTS-Signal muß gesetzt werden, damit der 26LS30-Schnittstellentreiber „enabled“ wird und das Sendesignal an den Port weitergibt.
v Das Kontrollregister 6 wird mit $FF beschrieben. Das hat zur Folge, daß im Leerzustand, also wenn sonst keine Zeichen übertragen werden, kontinuierlich binäre Einsen gesendet werden.
v Das DTR-Signal (Control der X.21-Norm) muß kontinuierlich auf logisch eins (OFF-Zustand für X.21) sein. Zusammen mit der Transmit-Leitung signalisiert das die Bereitschaft der DTE (DTE Ready).
v Die Zeichen beim Verbindungsaufbau müssen mit 7 Bit und ungerader Parität übertragen werden.
v Es ist nicht notwendig, daß der SCC während des Interruptzyklus des Prozessors einen Interruptvektor auf den Bus legt, da die Ebene 2 dem SCC fest zugeordnet ist und so der Prozessor den Verursacher des Interrupts aus der Prioritätsebene des Interrupts bestimmen kann. Der Level-2 Interrupt Handler stellt über den modifizierten Interruptvektor, den er aus dem SCC liest, die Ursache des Interrupts[24], die in den niederwertigen 4 Bit des Interruptvektors kodiert ist, fest.
v Bevor InitSCCStuff die SCC-Interrupts zuläßt, muß die ISR ReceiveCall, die ankommende Anrufe entgegennimmt, in der Level2IntTable installiert werden. Damit bei der Deinstallation des ISDNLAP der alte Zustand wiederhergestellt werden kann, ist es notwendig, den Inhalt der Level2IntTable zu retten, bevor er durch einen Zeiger auf ReceiveCall überschrieben wird.
v ISDNLAP verwendet in der Verbindungsaufbauphase einen externen Takt von 9600 Hz, der von der GSX-Schnittstelle geliefert wird.
Der Verbindungsaufbau erfolgt über GSXI-Schnittstellen von Nixdorf und ein modifiziertes X.21-Protokoll. Von den seriellen Ports des Macintosh Plus führen zu wenig Verbindungen zum SCC, so daß das Indicate-Signal der X.21-Empfehlung, das den Status der Verbindung anzeigt, nicht verfügbar ist[25]. Der Zustand der Verbindung kann also nicht direkt an der Schnittstelle abgelesen werden. Dadurch wird es notwendig, daß die Leitung vor dem Senden eines Nutzpaketes durch den Austausch von Kontrollpaketen explizit geprüft wird.
Weiter sind sämtliche Timeouts der X.21-Norm bei Verbindungsproblemen für eine Übertragung von AppleTalk-Paketen[26] über eine PBX zu lang. Um dem Benutzer des ISDN-AppleTalk im Fehlerfall lange Wartenzeiten zu ersparen, müssen sie verkürzt und den Vermittlungszeiten einer PBX angepaßt werden. Im öffentlichen ISDN können diese durchaus länger sein. Hier fehlt aber zur Zeit die Vergleichsmöglichkeit.
LineStatus = (connected, called, inProcess, notConnected, timeout, busy);
Die Zustände einer Verbindung werden durch den Typ LineStatus repräsentiert. In der globalen Variablen IsUp wird der aktuelle Zustand einer Verbindung festgehalten.
Das Wählen beim Verbindungsaufbau übernimmt die Prozedur DialUp. Als einziger Parameter wird der Prozedur die zu wählende Rufnummer übergeben. Über das Funktionsergebnis wird der Zustand der Verbindung vom Typ LineStatus geliefert.
FUNCTION DialUp(Number: ISDNNumber): LineStatus;
Eigentlich wäre es sinnvoll, als erstes den Zustand der DCE zu prüfen. Das ist aber durch das Fehlen des Indicate-Signals nicht möglich. Besteht eine Verbindung (HisAddress <> 0), so bricht DialUp diese durch den Aufruf von HangUp ab. Andernfalls wird nur der SCC neu initialisiert. Das ist notwendig, um eventuelle Fehlerzustände, wie eine unterbrochene Verbindung, zu beheben[27]. Anschließend werden die Receive-Interrupts gesperrt, damit ankommende Zeichen nicht durch ReceiveCall als ankommender Ruf interpretiert werden. Nachdem die Wahlzeichen (Rufnummer) mit zwei SYN-Zeichen am Anfang und einem ‘+’-Zeichen am Ende versehen wurden, signalisiert die DTE durch Senden von kontinuierlichen 0-Bits und durch Fallenlassen des Control-Signals (DTR) der Schnittstelle den Wunsch zum Verbindungsaufbau. Nun muß die Wahlaufforderung der X.21-Schnittstelle (‘+’-Zeichen) abgewartet werden. Dabei ist zu beachten, daß die Schnittstelle mit 7 Bit und ungerader Parität sendet. Wurde innerhalb einer bestimmten Zeitspanne keine Wahlaufforderung empfangen, so wird der Wählvorgang mit der Fehlermeldung Timeout abgebrochen.
Andernfalls können die Wahlzeichen an die Schnittstelle übermittelt werden, indem in einer Schleife immer dann, wenn der Transmit-Puffer des SCC leer ist, ein Zeichen übertragen wird[28]. Gleichzeitig werden ankommende Zeichen (‘+’) gelesen. Sind die Wahlzeichen übertragen, so muß die Transmit-Leitung wieder kontinuierliche 1-Bits senden.
Die Schnittstelle antwortet auf die Wahlzeichen mit der Anschlußkennung, die entweder eine Fehlermeldung oder die Rufnummer der Gegenstation enthält. Um diese einzulesen, muß gewartet werden, bis keine ‘+’-Zeichen mehr ankommen. Dabei ist zu beachten, daß die Anschlußkennung ihrerseits ‘+’-Character enthalten kann und von zwei SYN-Zeichen angeführt wird[29]. Die Anschlußkennung ist beendet, wenn nur noch kontinuierliche 1-Bits gelesen werden. Wurde nach einer bestimmten Zeit noch keine Anschlußkennung empfangen, so wird mit einem Timeout abgebrochen.
Die empfangene Anschlußkennung gibt Auskunft über den Verlauf des Verbindungsaufbaus. Daher wird getestet, ob die Anschlußkennung ein ‘*’-Zeichen enthält. Ist das nicht der Fall, so wurde nicht die Rufnummer der Gegenseite, sondern eine Fehlermeldung empfangen. Enthält die Anschlußkennung die Zeichenfolge ‘+21+’, so handelt es sich um eine Besetztmeldung. DialUp liefert als Funktionsergebnis busy. Bei allen anderen Fehlercodes ist der Fehler nicht zu beheben und eine Wahlwiederholung hat keinen Sinn. Ist in der Anschlußkennung jedoch die Rufnummer der Gegenseite enthalten, so wird durch die Prozedur InitHDLCStuff der SCC für die Paketübertragung initialisiert. Durch das Fehlen des Indicate-Signals ist nicht erkennbar, ob und wann die Leitung wirklich geschaltet ist und für die Übertragung zur Verfügung steht. Dabei kann es bis zu 1,5 Sekunden dauern, bis eine Verbindung durchgeschaltet ist und die Übertragung sich stabilisiert hat. Daher liefert DialUp als Funktionsergebnis höchstens inProcess und noch nicht connected. Im Anschluß muß bei der dynamischen Node-Adressierung mittels AcquireAddress durch die Übermittlung von Enquiry Control Frames geprüft werden, ob eine Verbindung wirklich besteht. Wird innerhalb einer gewissen Zeit kein Acknowledge Control Frame erwidert, so bedeutet das, daß keine Verbindung geschaltet wurde und die Verbindung wird durch Aufruf der Prozedur HangUp zurückgesetzt.
Bei der Initialisierung des LAP wird durch InitSCCStuff in der Level2IntTable eine Interruptserviceroutine ReceiveCall installiert, die einen ankommenden Anruf entgegennimmt.
Wird ReceiveCall über die Interrupttabelle gerufen, so prüft sie zunächst, ob von der Schnittstelle zur Signalisierung eines ankommenden Anrufs BEL-Zeichen gesendet werden. Wurde innerhalb einer bestimmten Zeit ein BEL-Zeichen empfangen, so wird über die Funktion IsMPPOpen anhand der globalen Variablen PortBUse geprüft, ob AppleTalk initialisiert ist. Trifft dies zu, dann wird durch Zurücksetzen des DTR-Signals (control) im Write-Kontrollregister 5 der Schnittstelle die Annahme des Anrufs (call accept) signalisiert. Danach sendet die Schnittstelle die Anschlußkennung, die in der Variablen ConnectedTo abgelegt wird.
Ein Auswerten der Anschlußkennung auf Fehlermeldungen ist nicht erforderlich, da die Schnittstelle bei Fehlern normalerweise keine Verbindung aufbaut. Wurde eine Anschlußkennung erhalten, so wird der Zähler LastTransmission zurückgesetzt und das ISDNLAP und der SCC mit InitHDLCStuff für die Paketübertragung initialisiert. Die Variable IsUp zeigt nun mit dem Wert InProcess an, daß eine Verbindung im Aufbau ist. Da das Indicate-Signal nicht verfügbar ist, muß die Leitung explizit getestet werden. Erst dann erhält IsUp den Wert called. Schließlich wird durch die Prozedur FilterNumber der Anschlußkennung die Rufnummer der anrufenden Station entnommen und in einem Zwischenspeicher festgehalten. Tritt ein Timeout oder ein anderer Fehlerzustand auf, so wird die Verbindung durch HangUp zurückgesetzt. Bevor ReceiveCall beendet wird, müssen durch Beschreiben des Write-Kontrollregisters die Interrupts des SCC wieder zugelassen werden (RHIUS).
Nachdem es nun möglich ist, Verbindungen aufzubauen, muß auch ein Abbruch erfolgen können. Ein Verbindungsabbau ist immer dann notwendig,
v wenn eine Verbindung zu einem Node besteht, dessen Node ID nicht mit der Zieladresse des nächsten zu sendenden Paketes übereinstimmt.
v wenn ein Fehler beim Verbindungsaufbau oder der Node-Adressierung auftritt.
v wenn eine bestimmte Zeit kein Paket mehr übertragen wurde.
v wenn die Gegenseite eine Verbindungsauflösung fordert.
v wenn eine Verbindung unvorhergesehen unterbrochen wurde und der SCC wieder für einen erneuten Verbindungsaufbau initialisiert werden muß.
Der Umstand, daß der Macintosh das Indicate-Signal nicht auswerten kann, beeinträchtigt nicht nur den Verbindungsaufbau, sondern auch den Abbau einer Verbindung.
Ein aktiver Verbindungsabbau wird dann durchgeführt, wenn beim Verbindungsaufbau ein Fehler aufgetreten ist oder eine bestehende Verbindung unterbrochen werden muß, da ein Paket an eine andere Station zu übertragen ist.
Zum Abbrechen einer Verbindung im ISDNLAP dient die Prozedur HangUp. Aufgerufen wird sie mit einem Parameter quitpacket, der angibt, ob die Gegenseite durch ein Kontrollpaket über den Verbindungsabbau informiert werden soll.
PROCEDURE HangUp(quitpacket: BOOLEAN);
Nachdem der SCC angelesen ist und die Master Interrupts abgeschaltet sind, sendet HangUp, falls gewünscht, durch Aufruf von TransmitMessage ein HUP-Paket an die Gegenstelle. Danach wird kurz gewartet, bis das Paket die Gegenseite erreicht hat und gelesen wurde. Schließlich wird die Verbindung abgebrochen, indem kontinuierlich 0-Bits gesendet werden und das DTR (Control) in den OFF-Zustand versetzt wird.
Da das Indicate-Signal fehlt, um feststellen zu können, wann die Verbindung abgebaut und die Schnittstelle wieder bereit (DCE Ready) ist, bleibt nur die Möglichkeit eine bestimmte Zeit zu warten und dann das ISDNLAP und den SCC mit InitSCCStuff neu zu initialisieren.
Ursache für den passiven Verbindungsabbau ist, daß seit der Übertragung des letzten Paketes eine Verbindung bestanden hat und eine bestimmte Zeit kein Paket mehr übertragen wurde. Durch den passiven Verbindungsabbau wird auch eine eventuelle Fehlersituation, z.B. eine Unterbrechung der Verbindung, behoben.
Das Testen und Abbrechen einer Verbindung durch einen Timeout übernimmt die Prozedur Terminator. Hier handelt es sich um eine VBL-Task, die bei der Installation des LAP durch InitLAP in die VBLQueue des Betriebssystems eingehängt und periodisch gerufen wird.
Unmittelbar nach dem Aufruf besorgt sich Terminator die globalen Variablen des ISDNLAP und erhöht den Zähler LastTransmission, der angibt wieviele Taskaufrufe seit der letzten Paketübertragung stattgefunden haben. Hat dieser Zähler einen bestimmten Wert TerminateDelay überschritten, und besteht nach der Variablen IsUp eine Verbindung, so löst die VBL-Task die Verbindung mit HangUp(TRUE) aus. Bevor die Task beendet wird, muß in VBLEntry der vblCount wieder auf den Startwert TaskTime gesetzt werden.
Normalerweise erkennt eine DTE anhand des Indicate-Signals, daß von der Gegenseite die Verbindung abgebrochen wird. Da das hier nicht möglich ist, muß eine abbrechende Station ihre Gegenstation durch Senden eines HangUp-Paketes über den bevorstehenden Verbindungsabbau informieren. Ohne dieses Kontrollpaket würde die Verbindung zur Gegenstation einfach abgebrochen und diese wäre erst nach dem nächsten Timeout wieder erreichbar. Erhält also eine Station ein HUP-Paket, so weiß sie, daß die Gegenseite die Verbindung abzubrechen wünscht und ruft ihrerseits die Prozedur HangUp, um das ISDNLAP für den Verbindungsaufbau neu zu initialisieren. Geht dieses Paket verloren, so wird spätestens nach dem Ablauf des Timers LastTransmission die Initialisierung für einen erneuten Verbindungsaufbau vorgenommen.
Beim Öffnen des .MPP-Treibers muß das ISDNLAP die Node ID des Knotens auf dem Netzwerk registrieren. Das geschieht im ISDNLAP durch Übertragung von ENQ-Paketen an den NameServer, der sich die Rufnummer und die Node ID ankommender ENQ-Pakete merkt. Durch die Verbindungssteuerung des ISDNLAP werden alle Broadcast-Pakete an den NameServer geleitet. Das RTMP sendet beim Öffnen des .MPP zwei Broadcast-Pakete ($FF) an das Netz (Abb.B.3.7). Dabei werden zum Testen der Verbindung auch ENQ-Pakete an den NameServer übertragen und die Eindeutigkeit geprüft. Ein explizites Anwählen des NameServers zur Registrierung der Node ID ist daher nicht notwendig. Zusätzlich werden bei jedem Anwählen des NameServers bei einem Name LookUp ENQ-Pakete übertragen.
FF 07 01 00 06 01 01 05 01 .........
Abb. B.3.7: Ein RTMP-Paket an den NameServer beim Öffnen des .MPP-Treibers
Nach jedem Aufbau einer Verbindung werden zuerst ENQ-Pakete übertragen. Da durch das fehlende Indicate-Signal nicht erkannt werden kann, wann eine Verbindung verfügbar ist, muß die Verbindung explizit getestet werden. Dabei kann gleichzeitig festgestellt werden, ob die eigene Node ID eindeutig ist und auf der Gegenseite die gewünschte Station angetroffen wird. Ein nachträgliches Ändern der Node ID kommt nur dann vor, wenn sich eine Station nicht am NameServer angemeldet hat.
Abb. B.3.8: Dynamische Node-Adressierung im ISDNLAP
Zur dynamischen Node-Adressierung und zum Testen der Verbindung wurde für ISDNLAP die Pascalprozedur AcquireAddress implementiert. Beim Aufruf wird der Prozedur ein Parameter Destination übergeben, der die Zieladresse der ENQ-Pakete enthält. Bei der Rückkehr teilt die Funktion mit, ob eine Verbindung zur angewählten Station hergestellt werden konnte und ob die lokale Node ID gültig ist.
FUNCTION AcquireAddress(Destination: BYTE): BOOLEAN;
AcquireAddress testet zuerst, ob die Node ID der Station im gültigen Bereich liegt. Wenn nicht, wird die Node ID 1 gesetzt. Nach dem Initialisieren von Statusvariablen beginnt eine Schleife, die terminiert, wenn eine gültige Adresse vorliegt oder die Anzahl der Versuche für die Bestimmung einer Node ID überschritten ist. Innerhalb dieser Schleife wird in einer weiteren Schleife im Abstand von jeweils einer sechzigstel Sekunde ein ENQ-Paket an die übergebene Adresse Destination übermittelt. Die Gegenstation erhält ein ENQ-Paket und antwortet nur dann, wenn die Destination-Adresse mit ihrer Node ID übereinstimmt oder es sich um ein Broadcast-Paket handelt[30]. Somit kann der Sender feststellen, ob er die gewünschte Station erreicht hat.
Sobald das LAP ein ACK-Paket erhält, wird die Sendeschleife beendet. In der Variablen HisAddress wird dabei die Node ID des Knotens, zu dem eine Verbindung besteht, festgehalten. Wurde nach einer bestimmten Anzahl von Versuchen kein Paket empfangen, wird die Schleife unterbrochen. Wenn die Adresse bereits in Gebrauch ist, bestimmt AcquireAddress eine neue Node ID. Dabei wird berücksichtigt, ob es sich bei der ursprünglichen Node ID um eine Server ID handelte. Schließlich wird ein Zähler für die Anzahl der Versuche, eine gültige Node ID zu erhalten, erhöht. Wurde keine gültige Node ID ermittelt, so beginnt AcquireAddress erneut mit dem Senden von ENQ-Paketen und der Vorgang wiederholt sich (Abb. B.3.8). Als Resultat liefert die Funktion, ob eine gültige Node ID vorliegt und eine Antwort von der Gegenseite erhalten wurde.
Steht ein LAP-Paket zur Übermittlung an, so ruft der LAP Manager den ‘atlk’-Assembler-Glue LAPWrite und dieser anschließend die Funktion TransmitPacket.
Abb. B.3.9: Die Prozeduren der Verbindungssteuerung
Diese prüft zunächst, ob die Node ID des Knotens, zu dem eine Verbindung besteht, mit der Zieladresse des Paketes übereinstimmt. Ist das nicht der Fall, so muß eine neue Verbindung eingerichtet werden. Da die Node ID des NamesServers im allgemeinen nicht $255 ist, wird zuvor geprüft, ob es sich um ein Broadcast-Paket handelt und ob eine Verbindung zum NameServer existiert. Trifft das zu, so kann nach dem Testen der Übertragungsleitung mit AcquireAddress das Paket übertragen werden. Andenfalls muß zuvor über DoDialing die Nummer zur Node ID ermittelt und eine neue Verbindung abgebaut werden.
Tritt in DoDialing oder in AcquireAddress ein Fehler auf, so wird TransmitPacket mit einer Fehlermeldung verlassen. Bevor ein Paket übermittelt wird, ist es notwendig, daß die Adresse der Gegenstation mit der Zieladresse des Paketes übereinstimmt, um unnötigen Verbindungsauf- und -abbau beim Betreiben einer Brücke zu vermeiden. Über TransmitFrame wird das Paket schließlich gesendet.
Den Aufbau einer Verbindung in TransmitPacket übernimmt DoDialing. Übergeben wird dieser Prozedur nur die Rufnummer des NameServers, mit der diese zunächst die Prozedur FindNumber ruft, um die Rufnummer der Zieladresse des anstehenden Paketes zu ermitteln.
FUNCTION DoDialing(Number: ISDNNumber): LineStatus;
Bei erfolgreicher Suche wird durch DialUp eine Verbindung zur ermittelten Rufnummer hergestellt. Andernfalls bleibt die Verbindung zum NameServer bestehen. Ist die Rufnummer besetzt, so wird nach kurzer Wartezeit die Anwahl solange wiederholt, bis der Verbindungsaufbau Erfolg hatte, ein Fehler aufgetreten ist oder nach einer Anzahl von Versuchen die Leitung immer noch nicht frei ist. Hat DoDialing keinen Erfolg, so erfolgt eine Meldung an den Klienten.
Stehen nach der Übertragung keine weiteren Pakete zur Übermittlung an, so wird die Verbindung durch die VBL-Task Terminator abgebrochen. Beim Verbindungsabbau wird die Variable LastTransmission wieder auf Null gesetzt.
Beim Öffnen des .MPP wurde die Node ID und die Rufnummer jeder Station am NameServer registriert. Wird nun eine Rufnummer zu einer Node ID gesucht, so benutzt die Verbindungssteuerung des ISDNLAP die Funktion FindNumber, um vom NameServer die Rufnummer zu einer Node ID zu erfahren. Aufgerufen wird FindNumber dabei mit der Nummer des NameServers und der Node ID, für die eine Nummer zu ermitteln ist. Über den VAR-Parameter Number wird die Rufnummer zurückgeliefert. Wurde eine Rufnummer gefunden, so liefert FindNumber TRUE.
FindNumber prüft zuerst, ob es sich bei der gesuchten Adresse um die Broadcast-Adresse handelt (Abb. B.3.10). Trifft das zu, so wird die Nummer des NameServers zurückgeliefert. Befindet sich die gesuchte Node-Adresse im Zwischenspeicher Cache, so wird die in Cache gespeicherte Rufnummer zurückgegeben. Andernfalls wird die WDS für ein FND-Paket vorbereitet und das Paket mit TransmitPacket solange übertragen, bis entweder eine Nummer vom NameServer gesendet oder eine Anzahl von Paketen übertragen wurde. Findet der NameServer die gesuchte Node ID, so sendet er ein RND-Paket mit der zugehörigen Nummer. Anschließend wird die Verbindung zum NameServer abgebrochen, die gesuchte Nummer über den VAR-Parameter zurückgegeben und in der globalen Variablen Cache gespeichert. Liefert die unter der Rufnummer des NameServers erreichte Station nicht die gesuchte Nummer, so bleibt die Verbindung bestehen. Dadurch kann bei bekannter Rufnummer eine Station auch direkt, ohne den Umweg über den NameServer, angesprochen werden.
Abb. B.3.10: Der schematische Ablauf der Suche nach einer Rufnummer
Um die Anzahl der Rufnummern, die beim NameServer gesucht werden, und damit die Anzahl der Verbindungen zu reduzieren, wird in der globalen Variablen Cache die zuletzt gewählte Rufnummer mit zugehöriger Node ID festgehalten. Das Speichern und Auswerten übernimmt bei aktiv aufgebauten Verbindungen die Funktion FindNumber.
NumberCache = RECORD
StoredID: BYTE;
StoredNumber: ISDNNumber;
END;
Ebenso wird durch die Prozeduren ReceiveCall und ReceiveHeader die Nummer und Node ID derjenigen Station festgehalten, die zuletzt angerufen hat. Das Speichern der letzten Rufnummer führt zu einer erheblichen Reduzierung des Verkehrs, da es sehr wahrscheinlich ist, daß die gleiche Station mehrmals hintereinander angewählt wird.
PROCEDURE ClearCache(VAR Cache: NumberCache);
Wurde eine Node ID unter einer Rufnummer nicht erreicht, so wird Cache durch die Prozedur ClearCache gelöscht.
Mehrere Nummern im Cache zu halten erscheint nicht sinnvoll, da in den meisten Fällen nur eine Station zur selben Zeit angesprochen wird. Außerdem wird dadurch die Verwaltung der Nummern im Zwischenspeicher komplexer und steht in keinem Verhältnis zum Nutzen.
Die retardierende objektorientierte Verbindungssteuerung und die Zwischenspeicherung der Rufnummern führen zu einer Minimierung der notwendigen Verbindungen.
Nachdem eine Verbindung zwischen zwei Knoten aufgebaut ist, wird mit der Übertragung von (Kontroll-)Paketen begonnen. Dazu wird durch Aufruf der Prozedur InitHDLCStuff der SCC für eine Übertragung im HDLC-Modus initialisiert und eine Interruptserviceroutine ReceiveHeader zum Empfang eines Paketes eingerichtet.
Bevor der SCC beschrieben werden kann, muß er erneut angelesen werden. Danach kann der Reset des Ports und die Initialisierung für den Verbindungsaufbau erfolgen. Das Beschreiben der Kontrollregister wird über die Prozedur Write_SCC vorgenommen. In Detail lautet die Initialisierungssequenz für die Paketübertragung im HDLC-Mode wie folgt:
Register |
Wert |
Bedeutung der Werte |
9 |
$40/$80 |
Reset von Port B oder Port A. |
4 |
$20 |
Verwendung des HDLC-Mode. |
10 |
$80 |
CRC-Preset, NRZ-Kodierung. |
11 |
$28 |
Externer Receive- und Transmit-Takt. |
6 |
Node ID |
Nur Pakete mit dieser Node ID empfangen. |
7 |
$7E |
Bei idle HDLC-Flag (01111110) senden. |
5 |
$EB |
Transmitter aktivieren, RTS an, low. |
3 |
$DD |
8-Bit-Char. empfangen, Enter Hunt Mode (mit der Suche nach Flags beginnen), Receive CRC aktivieren, Adreßsuche beginnen, Receiver aktivieren. |
15 |
$8 |
DCD-Interrupts für die Maus zulassen. |
1 |
$11 |
Receive-Interrupts bei jedem Zeichen und Special Condition, Externe Interrupts für die Maus zulassen. |
9 |
$A |
Keinen Interruptvektor ausgeben und Master Interrupts zulassen. |
Abb. B.4.1: Initialisierungssequenz des SCC für den HDLC-Mode des ISDNLAP
Anmerkungen zur Initialisierungssequenz
der Paketübertragung:
v Der Reset eines Ports belegt verschiedene Kontrollregister des SCC bereits passend vor. Diese müssen daher nicht mehr initialisiert werden.
v Das RTS-Signal ist zu setzen, damit der 26LS30-Schnittstellentreiber „enabled“ und das Sendesignal an den Port weitergegeben wird.
v Das Kontrollregister 6 wird mit der Node ID der Station beschrieben. Dadurch werden nur Pakete mit der passenden Zieladresse empfangen.
v Das DTR-Signal (Control der X.21-Norm) muß kontinuierlich auf logisch Null (ON-Zustand für X.21) sein.
v Die Zeichen werden mit 8 Bit und ohne Parität übertragen.
v Im Gegensatz zu AppleTalk verwendet das ISDNLAP die NRZ-Kodierung (Abb. B.4.2). FM0-Signale können von der X.21-Schnittstelle nicht übertragen werden.
Abb. B.4.2: NRZ-Kodierung
v Während das ALAP einen internen Sendetakt verwendet und den Empfangstakt aus der Kodierung des ankommenden Bitstroms entnimmt, benutzt ISDNLAP sowohl zum Senden als auch zum Empfangen einen externen Takt von 64 kHz.
v Bevor InitHDLCStuff die SCC-Interrupts zuläßt, muß die Interruptserviceroutine ReceiveHeader, die den Header ankommender Pakete einliest, in der Level2IntTable installiert werden. Erst dann dürfen SCC-Interrupts zugelassen werden.
AppleTalk verwendet für die Übertragung von LAP-Paketen ein HDLC/SDLC-Paketformat[31]. Dieses Format wird auch im ISDNLAP beibehalten. Im Unterschied zum ALAP sendet ISDNLAP zwischen den Paketen (Idle) ständig Flags. Auch wird am Ende eines Paketes keine Abbruchsequenz übertragen.
... FlagFlagFlagHeaderDatenDatenDatenCRCFlagFlagFlag...
Der Unterschied liegt darin begründet, daß beim ISDNLAP der Sender auch zwischen den Paketen aktiv sein darf, da hier keine Kollisionen – wie bei einer Busstruktur – auftreten können. Zudem ist durch den externen Takt und die NRZ-Kodierung keine gesonderte Synchronisation notwendig.
ISDNLAP benutzt ähnliche Kontrollpakete wie das ALAP. Bekannte Typen von Kontrollpaketen, sofern verwendet, behalten ihre Bedeutung. Um die zusätzlichen Funktionen des ISDNLAP zur Suche nach Rufnummern und zum Abbau einer Verbindung erfüllen zu können, werden drei neue LAP-Kontrollpakettypen hinzugefügt[32]:
LAP-Typ |
ID |
Bemerkung |
lapENQ |
$81 |
Wird für die dyn. Node-Adressierung, das Registrieren der Rufnummer und zum Testen der Verbindung verwendet. |
lapACK |
$82 |
Im Gegensatz zu ALAP wird im ISDNLAP auf ein ENQ-Paket immer ein ACK-Paket erwidert. |
lapRTS |
$84 |
Hat keine Bedeutung für ISDNLAP. |
lapCTS |
$85 |
Hat keine Bedeutung für ISDNLAP. |
lapHUP |
$86 |
Signalisiert der Gegenseite einen Verbindungsabbau. |
lapFND |
$87 |
Pakettyp zur Suche nach der Rufnummer am NameServer. |
lapRND |
$88 |
Antwortpaket mit der gesuchten Rufnummer. |
Wünscht ein Klient des LAP ein Paket zu senden, so ruft er den LAPWrite-Code des LAP, auf den ein Zeiger in ATalkHk2 verweist. Dabei übergibt er unter anderem einen Zeiger auf die WDS des LAP, in der das zu sendende Paket enthalten ist. Die LAPWrite-Prozedur des ISDNLAP ist eine Glueroutine, die die registerbasierenden Parameter für den Aufruf der eigentlichen Pascal-Senderoutinen auf den Stack legt. Schließlich wird von LAPWrite die Funktion TransmitPacket mit einem Zeiger auf die WDS gerufen.
FUNCTION TransmitPacket(WDS: WDSEntryPtr): TransmitStatus;
TransmitPacket prüft vor dem Senden des Paketes die Verbindung zur Gegenstation und stellt sie notfalls her (vgl. B.3.4.2). Ist die Gegenstation erreichbar, so wird die Funktion TransmitFrame gerufen, welche die eigentliche Übertragung übernimmt. Übergeben wird dieser Senderoutine nur ein Zeiger auf die WDS.
FUNCTION TransmitFrame(WDS: WDSEntryPtr): TransmitStatus;
Nachdem die Funktion TransmitFrame das Paket übertragen hat, kehrt sie mit einem positiven Funktionsergebnis zu TransmitPacket zurück (vgl. Abb. B.4.3). Konnte keine Verbindung zur Gegenstation aufgebaut werden oder ist bei der Übertragung des Paketes durch TransmitFrame eine Unregelmäßigkeit aufgetreten (fehlender Takt, Underrun, etc.), so kehrt TransmitPacket mit einer Fehlermeldung über LAPWrite zum Klienten zurück.
Abb. B.4.3: Die Aufrufkette beim Senden eines Paketes
Bevor die Prozedur TransmitFrame mit dem Senden des in der WDS verpackten Paketes beginnt, rettet sie mit der Inline-Prozedur SaveAndSetSR das Statusregister des M68000 und verhindert durch das Setzen des Wertes $2600, daß Interrupts die Übertragung eines Paketes stören. Die Bearbeitung eines Interrupts während der Übertragung eines Paketes würde eine Verzögerung zwischen dem Senden von zwei Zeichen zur Folge haben. Diese führt zu einem Underrun des SCC, worauf je nach Stand des EOM-Bits erst die CRC-Prüfsumme oder sofort ein Flag übertragen und somit das Paket unterbrochen wird.
Nachdem LastTransmission zurückgesetzt wurde, wartet TransmitFrame bis das EOM-Bit im Read-Kontrollregister 0 gelöscht wird. Erst danach ist das vorangegangene Paket vollständig übertragen. Um zu gewährleisten, daß eine Warteschleife terminiert[33], wird sie mit einem Timeout versehen. Dabei kann in Interrupts oder während die Interrupts gesperrt sind, die Systemzeit (Ticks) nicht verwendet werden, da diese deaktiviert ist. Wird das EOM-Bit bis zum Ablauf des Timeouts nicht zurückgesetzt, so wird das Statusregister wiederhergestellt und TransmitFrame mit einer Fehlermeldung verlassen.
Andernfalls wird die Struktur des übergebenen WDS durchlaufen (vgl. Abb. 5.2.2). Dabei werden die einzelnen Zeichen der WDS entnommen und durch Beschreiben des Write-Datenregisters gesendet (SCCWrData^ := BYTE;). Vor dem Senden des nächsten Zeichens muß anhand des TxBufferEmpty-Bits in Read-Kontrollregister 0 jeweils geprüft werden, ob das vorangegangene Zeichen bereits übertragen wurde.
Enthält das Längenfeld der WDS die Länge 0, so ist die Struktur bis zum Ende durchlaufen und alle Daten sind übertragen. Unmittelbar nach dem letzten Zeichen muß nun das EOM-Bit zurückgesetzt werden. Damit wird der SCC veranlaßt, die CRC zu senden, sobald der Transmit-Puffer leer ist. Erst wenn das Paket vollständig übertragen ist, wird das EOM-Bit vom SCC wieder gesetzt. Danach wird das Statusregister wiederhergestellt und die Funktion TransmitFrame mit dem Resultat TransmitOk verlassen.
FUNCTION TransmitMessage(destAddr: BYTE; Message: BYTE): TransmitStatus;
Eine weitere Prozedur TransmitMessage übernimmt das Zusammenbauen eines WDS für ein Kontrollpaket und das Senden über TransmitFrame. Als Parameter werden die Zieladresse und der Typ der Meldung übergeben.
Zur Erkennung von Übertragungsfehlern wurde im ISDNLAP eine CRC-Prüfsumme vorgesehen. Der SCC unterstützt eine automatische CRC-Berechnung im Zusammenhang mit HDLC-Paketen. Unmittelbar nach der Übertragung eines Frames wird das EOM-Bit gesetzt, worauf ein Underrun des Transmitters das Senden der CRC veranlaßt. Die Überprüfung der CRC erfordert nur geringen Zeit- und Programmieraufwand. Bei der Paketübertragung können folgende Fehler auftreten:
EOM-Bit
gesetzt
Das EOM-Bit wird nicht zurückgesetzt, da der vorangegangene Frame nicht beendet wurde (Ursache ist z.B. ein Fehlen des Taktes).
Das
Transmit-Puffer wird nicht leer,
da über die Schnittstelle kein externer Takt empfangen wird[34].
Underrun
Tritt auf, wenn sowohl der Transmit-Puffer als auch das Transmit-Shift-Register leer wird. Ist das EOM-Bit nicht gesetzt, so werden Flags gesendet. Der Empfänger erhält ein Paket mit falscher Länge und falscher CRC. Ein Underrun des Transmitters kann in TransmitFrame kaum vorkommen, da (bis auf den Debugger) alle Interrupts gesperrt sind. Ein Testen auf einen Underrun und eine gesonderte Fehlerbehandlung[35] ist daher nicht notwendig. Wird nach einem Underrun der Rest des Paketes gesendet, so wird er entweder durch die Adreßsuche der Gegenstation ignoriert oder er wird als fehlerhaftes Paket mit falscher CRC verworfen.
Empfängt der SCC ein anderes Zeichen als ein Flag, so vergleicht er dieses erste Zeichen eines ankommenden Paketes (Zieladresse) mit der in Write-Kontrollregister 6 gesetzten Node ID. Stimmen die Adressen überein oder handelt es sich um ein Broadcast-Paket, so fordert der SCC beim Prozessor einen Interrupt an. Über die Level-2 Interrupt Vector Table wird die Pascal-Prozedur ReceiveHeader des ISDNLAP gerufen.
PROCEDURE ReceiveHeader;
Diese Prozedur setzt zuerst den Zähler der Taskaufrufe seit der letzten Paketübertragung (LastTransmission) zurück und besorgt sich aus den lokalen .MPP-Variablen einen Zeiger auf die Read Header Area (RHA), in die der Kopf des Paketes gelesen wird. Anschließend ruft sie die Funktion ReceiveFrame, um die ersten 5 Bytes in die RHA einzulesen. Der Parameter NoDiscard gleich TRUE gibt an, daß, falls vorhanden, weitere Zeichen nicht verworfen werden dürfen.
Kehrt ReceiveFrame zurück, so liefert sie über incomingLength die Anzahl der tatsächlich gelesenen Bytes. Das Funktionsergebnis zeigt an, ob in ReceiveFrame ein Fehler aufgetreten ist. Wurden 5 Zeichen fehlerfrei gelesen, so wird anhand des Pakettyps des Headers in der RHA entschieden, wie mit dem Paket weiter zu verfahren ist.
Handelt es sich um ein fortgesetztes Paket (continuedFrame), so kann es sich nur um ein Datenpaket oder ein RND-Paket handeln. Ist die aus dem Packet Header ermittelte Paketlänge sinnvoll, so wird anhand des Pakettyps weiter verzweigt. Liegt der Typ im Bereich von $1–$80, so wird über die Glueroutine ReadDispatch der LAP Manager gerufen, der dann anhand des Pakettyps den passenden Protocol Handler ruft, um das restliche Paket einzulesen. Übergeben wird dabei ein Zeiger auf das 6te, nächste freie Byte in der RHA und die Anzahl der Bytes, die noch zu lesen sind[36]. Handelt es sich bei diesem Header um den Anfang eines RND-Paketes, so befindet sich im vierten Byte des Headers die Node ID und im fünften die Länge der ermittelten Rufnummer. Über die Prozedur ReceiveFrame wird die im Paket enthaltene Rufnummer eingelesen und als aktuelle Rufnummer (ActualNumber) übernommen.
Hat ReceiveFrame das Paket vollständig gelesen (closedFrame), so handelt es sich um ein Kontrollpaket des LAP. Wurde der Kopf eines ENQ-Paketes empfangen, muß nicht weiter gelesen werden, da diese Pakete nur aus der Zieladresse, der Quelladresse, dem Typ und den zwei CRC-Bytes bestehen. ReceiveHeader notiert die Quelladresse des Paketes in der globalen Variablen HisAddress und im Zwischenspeicher Cache. In IsUp wird festgehalten, daß die Station angerufen wurde. Schließlich wird über TransmitMessage ein ACK-Paket an die Gegenseite übertragen. Gehören die empfangenen Zeichen zu einem ACK-Paket, so merkt man sich die Quelladresse des Paketes. Je nachdem, ob diese mit der eigenen Node ID überein stimmt, d.h. die eigene Node ID eindeutig ist oder nicht, erhält die Variable AddrStatus den Wert InUse oder Valid. Wird ein HUP-Paket empfangen, so handelt es sich dabei um eine Aufforderung, die Verbindung abzubrechen. Das geschieht über die Prozedur HangUp, wobei der Parameter FALSE bedeutet, daß kein HUP-Paket an die Gegenseite gesendet wird.
Entspricht keiner der hier angeführten Typen dem des empfangenen Headers, so wird der Rest des Paketes durch die Prozedur PurgeRest verworfen. Bevor die Prozedur beendet wird, müssen in jedem Fall durch das Kommando Reset Highest Interrupt Under Service (RHIUS) die Interrupts des SCC wieder zugelassen werden.
FUNCTION ReceiveFrame(incomingPacket:FramePtr;BytesToRead:INTEGER; VAR incomingLength: INTEGER; NoDiscard:BOOLEAN):FrameStatus;
Der Funktion ReceiveFrame wird ein Zeiger auf einen Puffer (incomingPacket), die Anzahl der zu lesenden Bytes (BytesToRead), ein VAR-Parameter incomingLength, der angibt wieviele Bytes gelesen wurden, und ein Flag NoDiscard, welches anzeigt, ob überschüssige Bytes verworfen werden sollen, übergeben. Als erstes initialisiert die Funktion die lokalen Statusvariablen framedone und ReceiveFrame.
In einer annehmenden Schleife wird Zeichen für Zeichen gelesen, bis die Statusvariable framedone TRUE wird. Innerhalb der Schleife wird zuerst die Verfügbarkeit eines Zeichens geprüft. Ist kein Zeichen vorhanden, wird die Schleife mit einem Underrun-Fehler beendet. Enthält der FIFO-Puffer des SCC ein Zeichen, dann muß zuerst der Status des Zeichens in Read-Kontrollregister 1 gelesen werden. Signalisiert dieses Register „End of Frame“[37], so wird anhand des CRCErr-Bits festgestellt, ob der Frame unversehrt ist. Bei einem fehlerhaften Frame wird die Schleife mit einer Fehlermeldung verlassen. Andernfalls setzt ReceiveFrame die Statusvariable framedone und beendet beim nächsten Test der Abbuchbedingung die Schleife. Weiter muß anhand des Read-Kontrollregisters 1 geprüft werden, ob ein Overrun aufgetreten ist. Ist das nicht der Fall, so kann das Zeichen aus dem Read-Datenregister gelesen werden. Dazu wird der Zähler mit der Anzahl der gelesenen Zeichen inkrementiert und das Byte gelesen. Ist die gewünschte Anzahl von Bytes noch nicht gelesen, so wird das Zeichen in den übergebenen Puffer geschrieben. Wurden schon genügend Zeichen gelesen, so wird, je nach Wert des Parameters NoDiscard, die Schleife beendet oder mit dem Einlesen und Verwerfen der restlichen Zeichen des Frames fortgefahren.
Wurde die Schleife verlassen, und ist beim Empfangen des Frames ein Fehler aufgetreten, so „spült“ die Prozedur PurgeRest den Receiver des SCC. Schließlich kehrt die ReceiveFrame wieder zu ReceiveHeader zurück.
Die Prozedur ReceiveFrame wird aber nicht nur von ReceiveHeader zum Lesen des Paketkopfes verwendet, sondern auch von ReadPacket und ReadRest, um den restlichen Teil eines Frames zulesen. Dazu müssen lediglich die Parameter passend gesetzt werden. Der Aufruf von ReceiveFrame durch ReadPacket und ReadRest wird in Abschnitt B.5 beschrieben.
Bei der Übertragung eines Paketes können zahlreiche Fehler auftreten. Dem Aufrufenden werden sie über das Funktionsergebnis von ReceiveFrame mitgeteilt. Die wichtigsten Fehlersituationen und Maßnahmen zur Behebung werden hier erwähnt:
Frame
Size Error
Werden weniger als 5 Zeichen (Header & CRC) empfangen, so kann es sich nicht um ein gültiges LAP-Paket handeln und es wird verworfen. Zusätzlich ist im 4ten und 5ten Byte eines jeden Datenpaketes die Länge der Nutzdaten enthalten. Ist beim Aufruf von ReadRest die Anzahl der noch im Paket vorhandenen Zeichen länger als die Anzahl der zu lesenden Zeichen, so werden die überschüssigen Bytes verworfen. Anhand der Differenz von angeforderten und gelesenen Zeichen kann der Protocol Handler Fehler erkennen.
Frame Type
Wurde
ein Packet Header mit einem unbekannten Frametyp (Typ > $88) empfangen, so
wird der Rest des Paketes durch die Prozedur PurgeRest verworfen.
Overrun/Underrun Error
Tritt auf, wenn das LAP mit den Daten nicht synchronisiert bleibt, d.h unerwartet kein Zeichen mehr ankommt oder die ankommenden Zeichen nicht schnell genug entgegengenommen werden können. Dieser Fehler muß durch einen Reset der Statusflags und durch Leeren des FIFO behoben werden (Übernimmt die Prozedur PurgeRest).
Frame
Check Sequence
Ist die Prüfsumme (CRC) eines Paketes falsch, so wird mit PurgeRest der Receiver zurückgesetzt und ReceiveFrame mit einer Fehlermeldung beendet.
Ein Reset des Empfängers geschieht über die Prozedur PurgeRest. Diese setzt als erstes das SYNC/HUNT-Bit, so daß alle Zeichen bis zum nächsten Flag verworfen werden. Anschließend kann das FIFO des SCC geleert werden. Schließlich werden über das Kommando ResetErr die Statusbits in den Read-Kontrollregistern, die einen Fehler signalisieren, explizit zurückgesetzt.
Bei sorgfältiger Programmierung ist die Implementierung einer Paketübertragung mit einer Geschwindigkeit von 64 kbit/s auch mit einer 0.4 MIPS-Maschine, wie dem Macintosh Plus, in einer Hochsprache (Pascal) möglich.
Die Firma Apple hat für den Austausch des LAP für EtherTalk und andere Implementierungen von AppleTalk den LAP Manager geschaffen. Vorgesehen ist zum einen ein CDEV Netzwerk, das sich im System Folder befindet und im Kontrollfeld erscheint, zum anderen eine INIT-Resource (ID 18), die beim Systemstart den ausgewählten LAP-Code installiert[38].
Abb. B.5.1: Das CDEV Netzwerk und zugehörige ADEV im Kontrollfeld
Das CDEV Netzwerk durchsucht bei der Auswahl durch den Benutzer den System Folder nach Files vom Typ ADEV und zeigt alle alternativen AppleTalk-Implementierungen mit ihrem Icon und ihrem Namen im Fenster des Kontrollfelds an (Abb. B.5.1). Dabei fordert das CDEV von jedem ADEV Informationen an. Dies geschieht, indem der ‘adev’-Code eines jeden ADEV im System Folder zuerst vom CDEV durch einen GetADEV-Call mit folgenden Register-Parametern gerufen wird:
D0 101, Anhand dieses Wertes wird im ‘adev’-Code zu doGetADEV verzweigt.
D1 (long) Aktueller Wert der aktiven AppleTalk-Implementierung im Parameter RAM
D2 (long) Die Slotnummer, die vom letzten GetADEV-Call zurückgeliefert wurde. Beim ersten Aufruf 0.
Anhand des Wertes im D0-Register wird der GetADEV-Call (D0 = 101) vom SelectADEV-Call (D0 = 102) beim Anspringen des ‘adev’-Codes unterschieden[39]. Kehrt diese Routine aus dem ADEV zurück, so enthalten die Register folgende Werte:
D0 (byte) Das Statusflag des ADEV zeigt den Zustand an.
D2 (long) Die nächste Slotnummer, mit der GetADEV gerufen werden soll. Dieser Wert wird auch von SelectADEV verwendet.
A0 (pointer) Ein Zeiger auf den Namen des ADEV, der unter dem Icon im Kontrollfeld angezeigt wird.
In der ‘adev’-Resource des ISDNLAP wird der GetADEV-Call etwas anders als üblich behandelt. Eigentlich soll vom GetADEV-Call die Nummer des NuBus-Slots ermittelt werden, in der die Kommunikationskarte steckt. Da ISDNLAP aber ohne Karte arbeitet, wird, damit es beim Macintosh II zu keinen Konflikten kommt, der Wert $F, der außerhalb des Bereiches der möglichen Slot-Nummern ($9-$E) liegt, zurückgeliefert.
doGetADEV
Addq #1,D2 ; increase slot number
Cmp.B #$10,D2 ; If it is the second call
Beq.S nomore ; then let's get out of here
Move.B #0,D0 ; Mac Plus needs this result
Move.B #$F,D2 ; return our slot ID
Lea iconString,A0 ; get pointer to LAP name
Asr.L #8,D1 ; decode the selected LAP ID
Cmp.B D2,D1 ; if it is not ours
Bne.S notours ; then return
Move.B #-1,D0 ; else indicate 'not ours'
notours Rts ; go home
nomore Move.B #1,D0 ; indicate 'no more LAPs'
Rts ; go home
Abb. B.5.2: Die doGetADEV-Routine im ‘adev’-Code des ISDNLAP
Da ein ADEV unter Umständen mehrere Karten unterstützen kann, wird das ADEV vom CDEV solange gerufen, bis es über den Status im DO signalisiert, daß keine weitere Karte mehr vorhanden ist. Weiter zeigt der Code des ADEV mit $-1 im D0 Register an, daß seine Slot-Nummer mit der des zur Zeit ausgewählten LAP übereinstimmt (vgl. Abb. B.5.2). Dabei ist zu beachten, daß der Wert in den oberen drei Bytes des Parameter RAM-Wertes steht. Im A0-Register wird ein Zeiger auf den String zurückgeliefert, der unter dem Icon des ADEV im Kontrollfeld erscheinen soll. Dieses Icon wird vom CDEV der Resource Fork des ADEV-Files als ‘ICN#’-Resource entnommen und angezeigt.
Wählt der Benutzer durch Anklicken mit der Maus das gewünschte LAP aus, so wird der ‘adev’-Code des ADEV durch einen SelectADEV-Call über die Auswahl informiert. Dieser SelectADEV-Call wird mit folgenden Register-Parametern durchgeführt:
Beim Aufruf:
D0 102, Wert für den SelectADEV-Call
D2 (long) Der Wert, den das ADEV bei GetADEV geliefert hat.
Bei der Rückkehr:
D1 (high 3 bytes) Der Wert, der im Parameter RAM zu setzen ist; wird auch durch den AInstall-Aufruf an den ‘atlk’-Code übergeben.
Die doSelectADEV-Routine in ISDNadev.a ruft eine externe Pascal-Prozedur SetNumber, welche die Rufnummer des Servers aus einer Resource holt, sie in einer Dialogbox anzeigt und schließlich die geänderte Nummer wieder in die Resource zurückschreibt (Abb. B.5.3).
Abb. B.5.3: Eingabe der Nummer des NameServers bei der Auswahl
Pascal-Prozeduren retten nur verwendete Non Scratch-Register. Es kann aber durchaus sein, daß sich auch in Scratch-Registern (außer dem D2) Werte befinden, die nach dem Aufruf noch benötigt werden (Abb. B.5.4). Daher werden vor dem Aufruf von Pascal-Prozeduren aus Assembler die Scratch-Register (A0–A1, D0–D2) gesichert. Im D2-Register befindet sich beim Aufruf die Slot-Nummer des ausgewählten ADEV, die für das Parameter RAM in die oberen 3 Bytes des D1-Registers „geshiftet“ werden muß.
doSelectADEV
MoveM.L A0-A1/D0/D2,-(SP) ; save some registers
Jsr SETNUMBER ; call SetNumber
MoveM.L (SP)+,A0-A1/D0/D2 ; restore registers
Asl.L #8,D2 ; decode slot ID
Move.L D2,D1 ; and return it in D1
Rts ; go home
Abb. B.5.4: Die doSelectADEV-Routine im ‘adev’-Code des ISDNLAP
Bevor allerdings eine neue AppleTalk-Implementierung mit AInstall und LWrtInsert installiert werden kann, muß das alte LAP vom System Heap entfernt werden. Nachdem über SelectADEV die ‘adev’-Resource ausgewählt wurde, löscht das CDEV den ‘atlk’-Code des alten LAP aus dem System Heap. Da der zur Zeit installierte Code von seinem Resource File gelöst (detached) ist, muß sich das CDEV über einen LWrtGet-Call an dem LAP Manager die Adresse des aktiven ‘atlk’-Codes im System Heap besorgen. Durch einen AShutDown-Call an den aktiven ‘atlk’-Code wird dieser dann veranlaßt, sich mit einem LWrtRemove an den LAP Manager aus dem System Heap und dem ATalkHk2 zu entfernen.[40]
Der LAP Manager enthält eine INIT-Resource (ID 18), die beim Systemstart das ausgewählte LAP installiert. Welches LAP zuletzt aktiv war, entnimmt das INIT dem Parameter RAM (Abb. B.5.5).
Abb. B.5.5: Wert des ADEV im Parameter-RAM
Der ‘atlk’-Code des ausgewählten LAP wird in den System Heap geladen und dann über einen AInstall-Call installiert. Damit der Code nach dem Beenden des INIT im System Heap verbleibt, wird die ‘atlk’-Resource vom Resource File gelöst. Die Routinen GetADEV und SelectADEV in der ‘adev’-Resource werden allerdings nicht gerufen, d.h. es kann keine Rufnummer angegeben werden.
Nach dem SelectADEV-Call an den ‘adev’-Code wird der ‘atlk’-Code des LAP vom CDEV Netzwerk in den System Heap geladen und mit einem AInstall-Call gerufen. Dieser Code enthält ganz am Anfang einen Dispatcher. Wird die ‘atlk’-Resource gerufen, um ein LAP-Paket zu schreiben, so wird ganz zu Beginn des Codes bei atlkStart eingesprungen und zur LAPWrite-Routine verzweigt (vgl. Abb. B.5.6).
atlkStart
Bra.S LAPWrite ; first entry is the LAP write code
Cmpi.L #AInstall,D0 ; if it is an install call
Beq.S doAInstall ; do the installation
Cmpi.L #AShutDown,D0 ; if is a shutdown call
Beq.S doAShutDown ; then shut down
Moveq #-1,D0 ; If it is an unknown parameter
Rts ; go home
DC.B 'ATLKSTAR' ; debugger name
Abb. B.5.6: Einsprungstelle und Dispatcher im ‘atlk’-Code des ISDNLAP
Handelt es sich jedoch um einen Kontrollaufruf mit AInstall oder AShutDown, so wird der Dispatcher an der Position atlkStart+2 gerufen. Anhand des D0-Registers wird dann verzweigt. Enthält das D0-Register keinen zulässigen Wert, so kehrt die Routine mit einem negativen Wert als Fehlermeldung zum CDEV zurück. Bei der Implementierung der doAInstall-Routine ist folgende Registerbelegung einzuhalten:
Beim Aufruf:
D1 (long) Wert im Parameter RAM, wie er von SelectADEV gesetzt wurde.
D4 (Byte) Nummer des Ports, auf welchen sich diese Installation bezieht. Diese ist aber nur von Interesse, wenn mehrere Verbindungen gleichzeitig unterstützt werden.
Bei der Rückkehr:
D0 (long) enthält den Fehlercode; 0 bedeutet kein Fehler, ein negativer Wert signalisiert einen Fehler.
D1 (high 3 bytes) Neuer Wert für das Parameter RAM; bleibt unverändert.
Wird nun der ‘atlk’-Code zum Aufruf der AInstall-Routine angesprungen, so verzweigt der Dispatcher zu doAInstall. Dieser sichert zuerst den Wert des Parameter RAM im D1 und reserviert auf dem Stack Platz für das Funktionsergebnis. Danach wird die externe Pascal-Funktion InitLAP gerufen (vgl. Abb. B.5.7).
InitLAP initialisiert die globalen Variablen des ISDNLAP sowie das History, installiert die Tasks Terminator und TimeoutTask in der VBLQueue bzw. der Time Manager Queue und prüft, ob die Station eine gültige Node ID besitzt.
doAInstall
Move.L D1,-(SP) ; save value of parameter RAM
Clr.L -(A7) ; function result
Jsr InitLAP ; call our LAP init code
Move.B (A7)+,D0 ; get result
Beq.S error
Move #LWrtInsert,D0 ; if done call LAP Manager
error Lea atlkStart,A0 ; start of our LAP in A0
Move.B #PortBInt, D1 ; port B interrupts enabled
Move #1,D2 ; one try for a node ID
Move.L LAPMgrPtr,A1 ; call the LAP Manager
Jsr LAPMgrCall(A1) ; returns result in D0
Move.L (SP)+,D1 ; restore value of Para RAM
Rts ; go home
DC.B 'DOINSTAL' ; debugger name
Abb. B.5.7: Die doAInstall-Routine im ‘atlk’-Code des ISDNLAP
Nach der Rückkehr aus InitLAP wird das Funktionsergebnis ausgewertet. Ist bei der Initialisierung ein Fehler aufgetreten, so enthält das D0-Register den Wert 0 (FALSE) und der LAP Manager wird mit diesem Wert gerufen. Dieser liefert dann seinerseits über das D0 einen negativen Wert zurück, da kein LAP Manager-Befehl mit Code 0 vorhanden ist (vgl. Abb. B.5.7). Kehrt die AInstall-Routine mit einem negativem Wert im D0 zum CDEV zurück, so meldet dieses dem Benutzer über einen Stopalert einen Fehler.
Abb. B.5.8: Aufrufe bei der Installation des LAPWrite-Codes
Hat die Funktion InitLAP keinen Fehler entdeckt, so ruft doAInstall zur Installation des LAPWrite-Codes den LAP Manager mit LWrtInsert an der Stelle ATalkHk2+2 (vgl. Abb. B.5.8). Im A0-Register wird dabei die Anfangsadresse atlkStart des ’atlk’-Codes übergeben, an der ein Sprung zum eigentlichen LAPWrite-Code steht. Im einzelnen müssen folgende Parameter in den Registern übergeben werden:
Beim Aufruf:
A0 (pointer) Hier wird ein Zeiger auf den LAPWrite-Code übergeben.
D1 (byte) Das D1 enthält Flags. Für das ISDNLAP wird dem LAP Manager über diese Flags mitgeteilt, daß er die Port B-Interrupts zulassen soll.
D2 (word) Dieses Register enthält die maximale Anzahl der Versuche, die der LAP Manager unternehmen soll um eine gültige Node ID zu erhalten.
D4 (Byte) Nummer des Ports, auf welchen sich diese Installation bezieht. Diese ist nur von Interesse, wenn mehrere Verbindungen gleichzeitig unterstützt werden (Normalerweise 0).
Bei der Rückkehr:
D0 (long) enthält den Fehlercode; 0 bedeutet kein Fehler, ein negativer Wert signalisiert einen Fehler.
Kehrt der LAP Manager-Aufruf zurück, so enthält er im D0-Register die Fehlermeldung oder 0, falls der Aufruf erfolgreich war. Bevor AInstall mit einem RTS zum CDEV zurückkehrt, wird der gesicherte Wert des Parameter RAM in das D1-Register zurückgeschrieben.
Die eigentliche Senderoutine wurde in Pascal implementiert. Da die Parameter jedoch in Registern übergeben werden, ist es ratsam, eine kurze Glueroutine in Assembler zu verwenden. Dieser LAPWrite-Prozedur werden folgende Registerinhalte übergeben:
Beim Aufruf:
A0 (pointer) Hier wird ein Zeiger auf den Code übergeben, zu dem die LAPWrite-Prozedur nach dem erfolgreichen Senden eines Paketes zurückspringen soll.
A1 (pointer) Wird ein ENQ-Paket gesendet, so enthält das A1 das PortBUse-Byte, andernfalls einen Zeiger auf die Write Data Structure (WDS), in der das zusendende Paket enthalten ist.
A2 (pointer) Das A2-Register zeigt auf die lokalen Variablen des .MPP-Treibers (ABusVars).
D0 (byte) Das D0-Register enthält einen Wert ungleich 0, wenn ENQ-Pakete zu senden sind; ansonsten ist der Wert 0.
D1 (pointer) Im D1-Register steht ein Zeiger auf einen Code, der das Paket überträgt, falls das nicht durch das installierte LAP geschehen soll.
D2 (byte) Im niederwertigsten Byte des D2-Registers wird die Zieladresse des LAP-Paketes übergeben.
D4 (byte) Im D4-Register ist beim Senden von ENQ-Paketen ein Zähler enthalten, der anzeigt wieviele ENQ-Pakete noch zu senden sind; das ist allerdings nicht dokumentiert.
Bei der Rückkehr:
D0 (long) enthält den Fehlercode; 0 bedeutet kein Fehler, ein negativer Wert signalisiert einen Fehler[41].
Wird der LAPWrite-Code durch den Einsprung am Anfang des ‘atlk’-Codes gerufen, so sichert er zunächst einige Register. Damit die Maus auch bei mißlungenen Wählversuchen beweglich bleibt, wenn das ISDNLAP von einer VBL-Task gerufen wird, wird das Taskflag im Header der VBLQueue zurückgesetzt[42]. Anschließend legt er in der globalen Variablen MPPVars einen Zeiger auf die lokalen Variablen des .MPP ab, der im A2-Register enthalten ist. Steht ein ENQ-Paket zur Übertragung an (D0 <> 0), so wird durch InitSCCStuff mit dem Parameterwert TRUE der SCC für den Verbindungsaufbau initialisiert[43] und die globale Variable Guard auf FALSE gesetzt. Der InitSCCStuff-Aufruf darf erst dann geschehen, wenn die globale Variable MPPVars einen gültigen Wert besitzt, d.h. wenn der .MPP-Treiber geöffnet wurde. Andernfalls könnten Pakete empfangen werden, ohne daß AppleTalk initialisiert ist. Das D4-Register wird Null gesetzt, so daß keine weiteren ENQ-Pakete mehr gesendet werden[44]. Nach Wiederherstellung der Register kehrt LAPWrite zum Anfrufenden zurück.
LAPWrite
MoveM.L D1-D2/A0-A1/A3,-(A7) ; save registers,not D4
Bclr #$06, VBLQueue ; reset VBLTask flag
Lea Globals(PC),A3 ; get global variables
Move.L A2,(A3) ; pointer to MPPVars
Tst.B D0 ; is it an ENQ packet
Bne.S Enqs ; then jump to the enq
Tst.B Guard(A3) ; if we are still
Bne.S task ; sending then task
Clr.W -(A7) ; function result
Move.L A1,-(A7) ; push pointer to WDS
ST Guard(A3) ; lock out async tasks
Jsr TransmitPacket ; perform the Xmission
SF Guard(A3) ; allow calls to LAP
Tst.B (A7)+ ; get result
Beq.S itsOk ; if Xok then exit
task Move.L #CTSErr,D0 ; CTSErr to LAP Manager
Bra.S Exit
itsOk Move.L #NoErr,D0 ; noErr to LAP Manager
Bra.S Exit
Enqs SF Guard(A3) ; reset guard
Move.B #TRUE,-(A7) ; check connection
Jsr InitSCCStuff ; call SCCInitStuff
Move.L #Zero,D4 ; kill ENQ loop of MPP
Exit MoveM.L (A7)+,D1-D2/A0-A1/A3 ; restore registers
Jmp (A0) ; return to sender
DC.B 'WRITE ' ; debugger name
Abb. B.5.9: Die LAPWrite-Glueroutine im ‘atlk’-Code des ISDNLAP
Handelt es sich um ein normales Datenpaket (D0 = 0), so muß anhand der globalen Variablen Guard geprüft werden, ob nicht bereits ein Paket gesendet wird. Das ist notwendig, da andernfalls der Verbindungsaufbau das ISDNLAP durch eine asynchrone Task, die erneut ein Paket sendet (z.B. im Chooser), unterbrochen werden könnte. Das hat jedoch unter Umständen negative Auswirkungen auf die Verbindungssteuerung, da eine Unterbrechung den Status einer Verbindung verändern kann. Ein Paket wird daher nur gesendet, wenn Guard zurückgesetzt ist. Nachdem auf dem Stack Platz für das Funktionsergebnis der Pascal-Funktion TransmitPacket reserviert und ein Zeiger auf die WDS als Parameter auf den Stack geladen wurde (vgl. Abb. B.5.9), setzt LAPWrite das Flag Guard. Hat TransmitPacket das Paket übertragen, so wird das Flag wieder zurückgesetzt. Ist das Funktionsergebnis ungleich 0, so ist ein Fehler aufgetreten, der dem aufrufenden Klienten als ‘CTS Missing’-Fehler gemeldet wird.
Nachdem von der Interruptprozedur ReceiveHeader der Kopf eines Paketes in die Read Header Area eingelesen wurde, kann anhand des LAP-Typs entschieden werden, welcher Protocol Handler (PH) das weitere Einlesen übernimmt. Um den LAP Manager über ein ankommendes Paket zu informieren und ihn zu veranlassen, nach dem passenden PH zu suchen, wird ein LRdDispatch-Call an den LAP Manager gerichtet. Der Aufruf von LRdDispatch wird nicht direkt von ReceiveHeader vorgenommen, sondern geschieht über einen Assembler-Glue ReadDispatch, der folgendermaßen gerufen wird:
PROCEDURE ReadDispatch(RHA: Ptr; Rest: LONGINT; lapType: BYTE; MPPVars: ABusVarsPtr);
Dafür gibt es mehrere Gründe: Zum einen handelt es sich um eine Registerschnittstelle, die von Pascal aus nur mit Mühe zu bedienen ist. Zum anderen kehrt der LRdDispatch-Call nur dann zur aufrufenden Prozedur ReadDispatch zurück, wenn für das Paket kein passender Protocol Handler gefunden wurde. Ansonsten kehrt der vom LAP Manager gerufene PH direkt zur Prozedur ReceiveHeader zurück, obwohl der LAP Manager nicht von dieser direkt, sondern über die Prozedur ReadDispatch mit LRdDispatch gerufen wurde[45]. Ein weiterer Grund, warum diese Prozedur in Assembler geschrieben wurde ist, daß das Aufrufen des Protocol Handlers zeitkritisch ist[46].
Beim Aufruf von LRdDispatch (D0=1) werden die vom LAP Manager benötigten Parameter in Registern übergeben, die wie folgt belegt sind:
A0 (pointer) Adresse eines Hardwareregisters; hier handelt es sich um SCCWrite; wird hier nicht benutzt.
A1 (pointer) Adresse eines Hardwareregisters; hier handelt es sich um SCCRead; wird hier nicht benutzt.
A2 (pointer) Ein Zeiger auf die lokalen .MPP-Variablen ABusVars.
A3 (pointer) Ein Zeiger, der auf das nächste freie Byte in der Read Header Area (.MPP RHA) zeigt; das 6te Byte der RHA, da der Header 5 Bytes lang ist.
A4 (pointer) Ein Zeiger auf die ReadPacket-Prozedur, die vom Protocol Handler gerufen wird, um weitere Teile des Paketes einzulesen.
A5 Wird gesichert und vom PH nach der Rückkehr aus ReadRest wiederhergestellt.
D0 (word) Enthält den Code, anhand dessen der LAP Manager erkennt, daß es sich um einen LRdDispatch-Aufruf handelt.
D1 (long) Enthält die Anzahl der Bytes des Paketes, die noch zu lesen sind. Entspricht dem Inhalt des Längenfeldes im Paket abzüglich der 2 Bytes für die Länge, die ja schon gelesen wurden! Die 2 Bytes für die CRC und die 3 Bytes des LAP-Headers werden dabei nicht mitgerechnet[47]!
D2 (byte) Das D2-Register enthält den LAP-Typ des Paketes, dessen Header gelesen wurde. Anhand dieses Typs wird ein Protocol Handler bestimmt, der das weitere Einlesen übernimmt.
Wird die Prozedur ReadDispatch von ReceiveHeader gerufen, so befinden sich die Parameter des Aufrufs auf dem Stack. Die Prozedur ReadDispatch holt diese vom Stack und legt sie in Registern ab. Anschließend wird für eine eventuelle Rückkehr zu ReadDispatch die Rückkehradresse auf den Stack gelegt (siehe Abb. B.5.10). Nun kann der LAP Manager gerufen werden.
ReadDispatch PROC EXPORT
RHA EQU 14 ; parameters on stack
Rest EQU 10
LAPType EQU 8
MPPVars EQU 4
Lea Read(PC),A4 ; load address of read
Move.L RHA(A7),A3 ; get Ptr to RHA
Move.L MPPVars(A7),A2 ; get Ptr to MPPVars
Move LAPType(A7),D2 ; get LAPType
Move.L Rest(A7),D1 ; get length of stuff to read
Move.L (A7),RHA(A7) ; put return address at position of RHA for return
Add #14,A7 ; get rid of params
Moveq #LRdDispatch,D0 ; code for LRdDispatch
Move.L LAPMgrPtr,A1 ; code of ATlkHook2
Jsr LAPMgrCall(A1) ; call the LAP Manager
Jsr PurgeRest ; if there is no PH, purge rest of packet
Rts ; go home
DC.B 'READDISP' ; debugger name
ENDP
Abb. B.5.10: Die ReadDispatch-Glueroutine im ‘atlk’-Code des ISDNLAP
Kehrt der LAP Manager zu ReadDispatch zurück, so wurde kein passender Protocol Handler gefunden und das Paket muß verworfen werden. Das geschieht über den Aufruf der Pascal-Prozedur PurgeRest, die den Rest eines Paketes verwirft. Diesen etwas ungewöhnlichen Aufrufmechanismus soll die Abbildung B.5.11 nochmals verdeutlichen.
Abb. B.5.11: Call-Mechanismus zwischen ISDNLAP und LAP Manager
Der LAP Manager ruft bei einem LRdDispatch-Call, falls vorhanden, den zum LAP-Typ des Paketes registrierten Protocol Handler. Dieser übernimmt nun mit Hilfe der Prozeduren ReadPacket und ReadRest das weitere Einlesen. Beim Aufruf von LRdDispatch durch ReadDispatch wurde die Adresse einer Sprungtabelle übergeben. Der PH findet so an der Adresse Read einen Sprung zu ReadPacket und an der Stelle Read+2 die Prozedur ReadRest.
Read PROC EXPORT ; this is my Read Dispatch Table
Bra.S ReadPacket ; call the ReadPacket code
Bra.S ReadRest ; ReadRest must start two byte after ReadPacket
Abb. B.5.12: Der Read Dispatcher im ‘atlk’-Code des ISDNLAP
Zwischen dem Lesen des 5ten Zeichens des Paketes durch ReceiveHeader, dem Aufruf des LAP Managers über ReadDispatch und dem Lesen des nächsten Zeichens durch ReadPacket dürfen nicht mehr als 360 µs verstreichen[48]. Andernfalls tritt ein Overrun des FIFO-Puffers im SCC auf. Das eigentliche Einlesen der Pakete übernehmen nicht die Prozeduren ReadPacket und ReadRest, sondern die Pascal-Prozedur ReceiveFrame. Da die Schnittstelle aber wieder „registerbased“ ist und ReadRest über einen Offset erreicht wird, ist eine Glueroutine in Assembler zwischengeschaltet. Ein Protocol Handler ruft die ReadPacket-Prozedur mit folgenden Register-Parametern[49]:
Beim Aufruf:
A3 (pointer) Hier steht ein Zeiger auf einen Puffer, in den die gewünschte Anzahl von Bytes gelesen werden soll.
D1 (word) Im D1-Register steht die Anzahl der Bytes, die insgesamt noch im Paket enthalten sind[50].
D3 (word) Das D3-Register enthält die Anzahl der Bytes, die von ReadPacket zu lesen ist; der Wert darf nicht 0 sein.
Bei der Rückkehr:
D0 (byte) enthält den Fehlercode; 0 bedeutet kein Fehler, ein negativer Wert signalisiert einen Fehler[51].
D1 (word) Das D1-Register enthält die Anzahl der Bytes, die nach ReadPacket noch im Paket verbleiben.
D2 Der Inhalt muß von ReadPacket gesichert werden.
D3 (word) Null falls die gewünschte Anzahl von Bytes gelesen wurde. Ist in ReadPacket ein Fehler aufgetreten, so wird ein Wert ungleich Null zurückgeben.
A0-A2 müssen gesichert werden.
A3 (pointer) Hier wird ein Zeiger übergeben, der auf ein Byte hinter das zuletzt gelesene Zeichen im Puffer zeigt.
ReadPacket rettet als erstes einige Register und reserviert dann auf dem Stack Platz für einen VAR-Parameter und das Funktionsergebnis von ReceiveFrame. Die Parameter des Aufrufs werden auf den Stack geladen. Zusätzlich übergibt ReadPacket die Adresse der lokalen Variablen und den Parameterwert NoDiscard (Abb. B.5.13). Das bewirkt, daß exakt die Anzahl der gewünschten Bytes gelesen und dann zum Aufrufenden zurückgekehrt wird.
ReadPacket
MoveM.L D1-D2/A0-A2,-(A7) ; save some registers
Subq #4,A7 ; space for BytesGot of ReceivePacket & result
Move.L A3,-(A7) ; push pointer to buffer
Move D3,-(A7) ; push # of bytes to read
Pea 8(A7) ; address of local variable
Move.W #NoDiscard,-(A7) ; read exactly the count of bytes and not more
Jsr ReceiveFrame ; read requested # of bytes
Move.B (A7)+,D0 ; get result of function
Move (A7)+,D2 ; get BytesGot
Move.L (A7)+,D1 ; D1 must not be modified
Sub D2,D3 ; calculate bytes still there
Sub D2,D1 ; rest of bytes - BytesGot
Add D2,A3 ; pointer behind last byte
MoveM.L (A7)+,D2/A0-A2 ; restore registers
Sub.B #1,D0 ; is it a continued frame?
Beq.S yes ; Yes => jump (Z-flag set)
Move.L #-1,D3 ; call failed (Z-flag clear)
yes Rts ; go home
DC.B 'READPACK' ; debugger name
Abb. B.5.13: Die ReadPacket-Glueroutine im ‘atlk’-Code des ISDNLAP
Zurückgeliefert werden von ReceiveFrame die Anzahl der gelesenen Zeichen und als Funktionsergebnis die Fehlermeldung. Anschließend werden die empfangenen Zeichen von den zu lesenden (D3) und noch zu lesenden Zeichen (D1) subtrahiert. Unbedingt beachten sollte man, daß, falls ReadPacket Erfolg hatte, das D0-Register bei der Rückkehr aus ReadPacket 0 (no error) enthalten muß. Andernfalls ist das D0 ungleich Null. Zusätzlich muß bei Erfolg das Zero-Flag gesetzt und sonst zurückgesetzt werden.
Nachdem so ein Paket Stück für Stück eingelesen wurde, muß der Protocol Handler als letztes die Prozedur ReadRest rufen. Wie der Name bereits suggeriert, liest diese Prozedur den Rest eines Paketes ein. Dabei werden mindestens zwei Zeichen, d.h. die zwei Byte lange Prüfsumme gelesen. Beim Aufruf durch den Protocol Handler und bei der Rückkehr zu diesem befinden sich in den Registern folgende Werte:
Beim Aufruf:
A3 (pointer) Hier steht ein Zeiger auf einen Puffer, in den die gewünschte Anzahl von Bytes gelesen werden soll.
D1 (word) Im D1-Register steht die Anzahl der Bytes, die insgesamt noch im Paket enthalten sind[52].
D3 (word) Das D3-Register enthält die Größe des Puffers, der den Rest des Paketes aufnimmt; kann 0 sein.
Bei der Rückkehr:
D0 (long) enthält den Fehlercode; 0 bedeutet kein Fehler, ein negativer Wert signalisiert einen Fehler[53].
D1 verändert (modified).
D2 Der Inhalt muß von ReadRest gesichert werden.
D3 (word) Null, falls genau die gewünschte Anzahl von Bytes gelesen wurde. Negativ, wenn das Paket -D3 Zeichen zu lang für den Puffer war und abgeschnitten werden mußte. Positiv, wenn der Puffer D3 Zeichen zu groß für das Paket war.
A0-A2 müssen gesichert werden.
A3 (pointer) Hier wird ein Zeiger übergeben, der 1 Byte hinter das letzte gelesene Zeichen im Puffer zeigt.
Ein Paket wird unter allen Umständen vollständig eingelesen. Schließlich ist diese Prozedur bei End of Frame (EOF) für das Überprüfen der CRC-Prüfsumme verantwortlich. Die Parameterübergabe findet wie bei ReadPacket statt. Der einzige Unterschied besteht darin, daß ReceiveFrame mit Discard gerufen wird, wodurch überschüssige Bytes verworfen werden (Abb. B.5.14).
ReadRest
MoveM.L D2/A0-A2,-(A7) ; save some registers
Subq #4,A7 ; space for BytesGot of ReceivePacket & result
Move.L A3,-(A7) ; push pointer to buffer
Move D3,-(A7) ; push # of bytes to read
Cmp D3,D1 ; buffer size < rest of bytes
Bge.S more ; read buffer size
Move D1,(A7) ; if not read remaining byte
more Pea 8(A7) ; addr of var for BytesGot
Move #Discard,-(A7) ; discard the rest of bytes
Jsr ReceiveFrame ; read in requested bytes
Move.B (A7)+,D0 ; get result of function, ; must contain result for PH Move (A7)+,D2 ; get bytesGot
Subq #2,D2 ; length without CRC bytes
Cmp.W D2, D3 ; # bytes read > buffer size
Ble.S toomuch ; then add buffer size
Add.W D3,A3 ; else
Bra.S goon ; add bytes read
toomuch Add.W D2,A3 ; pointer behind last byte
goon Sub.W D2,D3 ; bufferSize-bytesGot
MoveM.L (A7)+,D2/A0-A2 ; restore register
Tst.B D0 ; is it a LAP data frame?
; (set zero flag)
Rts ; go home
DC.B 'READREST' ; debugger name
ENDP
Abb. B.5.14: Die ReadRest-Glueroutine im ‘atlk’-Code des ISDNLAP
Zurückgeliefert werden von ReadRest die Anzahl der gelesenen Zeichen und als Funktionsergebnis die Fehlermeldung. Die empfangenen Zeichen, ohne die zwei CRC-Bytes, müssen von den zu lesenden (D3) und noch zu lesenden Zeichen (D1) subtrahiert werden. Unbedingt beachten sollte man wieder, daß das D0-Register bei der Rückkehr aus ReadPacket die Fehlermeldung enthalten muß. Zusätzlich muß das Zero-Flag entsprechend gesetzt werden.
Schließlich kehrt die ReadRest-Prozedur wieder zum Protocol Handler zurück. Dieser wiederum kehrt nicht zum LAP Manager oder zu ReadDispatch, sondern direkt zu ReceiveHeader zurück. Dort wird der Interrupt beendet und das unterbrochene Programm fortgesetzt (siehe Abb. B.5.11).
Wählt der Benutzer im Kontrollfeld ein anderes LAP als das ISDNLAP aus, so muß der Code des ISDNLAP deinstalliert und der belegte Speicher ordnungsgemäß zurückgegeben werden. Bevor der LAP Manager den ‘atlk’-Code aus den System Heap entfernt, wird deshalb vom CDEV Netzwerk die Routine doAShutDown in der ‘atlk’-Resource des ISDNLAP gerufen, die für die Deinstallation verantwortlich ist[54].
doAShutDown ; quit ISDN AppleTalk
MoveM.L A0/D1-D2,-(SP) ; save some registers
Clr.W -(A7) ; function result
Jsr RestoreOldLAP ; restore old LAP code Move.B (A7)+,D0 ; get the result
Beq.S notOk ; jump if error
Move #LWrtRemove,D0 ; set up an LWrtRemove notOk MoveM.L (SP)+,A0/D1-D2 ; restore registers
Move.L LAPMgrPtr,A1 ; call the LAP Manager
Jsr LAPMgrCall(A1) ; returns D0
Rts ; go home
DC.B 'DOSHUTDO'
Abb. B.5.15: Die doAShutDown-Routine im ‘atlk’-Code des ISDNLAP
Nachdem doAShutDown einige Register gerettet hat, ruft es die Pascal-Funktion RestoreOldLAP, die für die Deinstallation des ISDNLAP verantwortlich ist (Abb. B.5.15). Wurde diese Prozedur mit FALSE beendet, so wird der LAP Manager mit dem Wert 0 im D0-Register gerufen, worauf dieser eine Fehlermeldung im D0 liefert. Hatte die Deinstallation Erfolg, so wird der LAP Manager mit dem Code für LWrtRemove im D0-Register zum Entfernen des LAP-Codes veranlaßt.
RestoreOldLAP bricht zuerst eine eventuell bestehende Verbindung ab. Durch den Aufruf der Prozedur RestoreOldInterruptService wird dann der SCC deaktiviert und die Interruptserviceroutinen, die sich vor der Auswahl des ISDNLAP in der Interrupttabelle befanden, werden wieder installiert. Dabei ist zu beachten, daß vor einer Änderung an der Interrupttabelle die Interrupts des SCC ausgeschlossen werden müssen. Anschließend wird von RestoreOldLAP die VBL-Task Terminator aus der VBLQueue entfernt.
Abb. B.5.16: Das PortBUse-Byte
Schließlich muß in der globalen Betriebssystemvariablen PortUsePtr (Abb. B.5.16) der Wert $FF gesetzt werden[55]. Dadurch wird signalisiert, daß der .MPP-Treiber geschlossen wurde und bei einer erneuten Verwendung von AppleTalk zuerst geöffnet werden muß, damit der SCC wieder für Standard-AppleTalk initialisiert ist.
[1] »Wohin muß der Code geladen werden?« und »Wie groß wird das Programm?«
[2] Ein Pascal- und C-Compiler, sowie ein Macro-Assembler werden mitgeliefert.
[3] Dabei handelt es sich zumeist um sog. DEV-Files.
[4] Ein Befehl greift wortweise auf eine ungerade Adresse zu.
[5] Treten auf, wenn falsche Rückkehradressen auf dem Stack liegen.
[6] unzulässige Speicherreferenz.
[7] »Poor Man‘s Debugging«
[8] Schulthess, Froitzheim (Integration des Zugangs zu abgesetzten Druckern über PBX-Systeme)
[9] Mit MacsBug wird mit „F 00000000 40000000 '[ISDNInit]' “ nach der Startmarke gesucht und das History mit „DM“ angezeigt.
[10] Denny, Coad (Programmers Guide to Networking), S. 195.
[11] Apple Computer (Macintosh Programmers Workshop)
[12] Zudem muß auch das Handle wieder irgendwo aufbewahrt werden.
[13] Siehe hierzu Apple Computer (Inside Macintosh III), S. 211ff, „Routines that may move or purge memory“.
[14] Zum Zeitpunkt der Implementierung stand die Version 6.0.3 des Systems und die Version 6.1 des Finders zur Verfügung. Die aktuelle Version des AppleTalk-Treibers .MPP ist 52, der .XPP-Treiber liegt in der Version 1.2 vor. AppleShare wurde in der Version 2.01 verwendet. Gedruckt wurde mit einem Druckertreiber der Version 5.0.
[15] Siehe Abschnitt B.5.
[16] Siehe Abschnitt B.1.3.
[17] „Hard coded strings“ sollte man in Macintosh-Applikationen eigentlich nicht verwenden!
[18] Die detailierte Struktur der Datenobjekte kann dem Listing in Anhang F entnommen werden.
[19] Siehe hierzu Apple Computer (Inside Macintosh II), S. 349ff.
[20] Die Prozessoren 68020/30 unterscheiden Schreib- und Lesezugriffe durch ein Prozessorpin.
[21] Apple Computer (Inside Macintosh II), S. 196.
[22] Zilog (SCC Handbuch)
[23] Vgl. Zilog (SCC Handbuch).
[24] Zeichen angekommen, Ende des Frames etc.
[25] Vgl. hierzu auch Anhang D.
[26] Es kann bis zu 20 Sekunden dauern, bis eine Verbindung geschaltet ist.
[27] Eine unterbrochene Verbindung kann normalerweise anhand des Indicate-Signals erkannt werden.
[28] Der Takt wird von der Schnittstelle geliefert. Daher kann es vorkommen, daß bei Ausbleiben des Taktes der Transmit-Puffer nicht leer wird.
[29] Vgl. Abschnitt 7.2.1.
[30] Der SCC arbeitet im Address Search Mode.
[31] Vergleiche Paketformat des ALAP aus Abschnitt 4.2.4.1.
[32] Grundsätzlich ist der Bereich $81–$254 für die Pakettypen der LAP-Kontrollpakete reserviert. In Absprache mit Apple können ungenutzte Typen für eigene Zwecke verwendet werden.
[33] Fehlt der externe Takt, so wird z.B. das Transmit-Puffer niemals leer.
[34] z.B. durch Leitungsunterbrechung.
[35] Das Senden einer Abort-Sequenz, um die Gegenstation in den SYNC\HUNT-Mode zu bringen.
[36] Siehe auch Abschnitt B.5.3.1.
[37] Bei EOF steht das letzte Zeichen noch im FIFO!
[38] Vergleiche hierzu auch Abschnitt 6.2.3.
[39] Siehe Listing des 'adev'-Codes in Anhang F.
[40] Siehe Abbildung 6.2.5.
[41] Das ist nicht dokumentiert, aber für die Implementierung von größter Bedeutung.
[42] Bei der Auswahl eines Servers im Chooser war zu beobachten, daß die Maus bei gescheiterten Wählversuchen für die Dauer der Timeouts unbeweglich blieb. Da das LAP im Abstand von zwei Sekunden von einer VBL-Task gerufen wird, war das sehr störend. Diese Schwierigkeiten können vermieden werden, indem bei Aufruf von LAPWrite ein Flag im Header der VBLQueue zurückgesetzt wird. Man sollte sich jedoch darüber im klaren sein, daß unsaubere Programmierung hierbei zu Komplikationen führen kann.
[43] Auch wenn der LAPWrite-Code ersetzt wurde, manipuliert das AppleTalk vor dem Senden von ENQ-Paketen am Port B des SCC. Eine vor dem Senden von ENQ-Paketen aufgebaute Verbindung wird dadurch eventuell unterbrochen. Vgl. Froitzheim, Lupper (PluriLAP).
[44] Normalerweise werden bis zu 32 ENQ-Pakete gesendet.
[45] Zur besseren Verdeutlichung betrachte man die Abbildung B.5.11.
[46] Zwischen dem Lesen des 5ten und 6ten Zeichens dürfen nicht mehr als ca. 360 ms vergehen.
[47] Das herauszufinden kostete viel Zeit und Nerven.
[48] Vergleiche hierzu Apple Computer (Inside Macintosh II), S. 329.
[49] Vgl. Apple Computer (Inside Macintosh II), S. 326–327.
[50] Das ist zwar nicht dokumentiert, wird aber sowohl vom ursprünglichen ALAP-Code als auch vom Async AppleTalk-Code verwendet.
[51] Das ist nicht dokumentiert, aber für die Implementierung von größter Bedeutung. Die Dokumentation spricht nur von »modified«.
[52] Das ist zwar nicht dokumentiert, wird aber sowohl vom ursprünglichen ALAP-Code als auch vom Async AppleTalk-Code verwendet.
[53] Das ist nicht dokumentiert, aber für die Implementierung von größter Bedeutung. Die Dokumentation spricht nur von »modified«.
[54] Siehe auch Abschnitt 6.2.3.2.
[55] Siehe Apple Computer (Inside Macintosh), S. 305.