Betriebssystem: Device-Treiber unter MiNT, Teil 1

MiNT (und damit auch MultiTOS) bieten neue Konzepte für nachladbare Gerätetreiber, die wesentlich flexibler sind, als die bisherigen Möglichkeiten des TOS.

Mit diesen Treibern, im folgenden auch Device-Treiber genannt, ist zumindest theoretisch eine weitgehende Loslösung von der zugrundeliegenden Hardware möglich, so daß weniger Kompatibilitätsprobleme aufgrund unterschiedlicher Rechnerkonfigurationen auftreten. Wenn die Treiber richtig benutzt werden, können Anwendungsprogramme in vielen Fällen ohne jede Änderung neue Hardware benutzen, sobald ein Treiber dafür verfügbar ist.

Zur Zeit ist die Lage nicht besonders rosig: Es gibt praktisch nur die in MiNT eingebauten Treiber für die Standardschnittstellen der Ataris, die leider noch einige Mängel aufweisen. Außerdem gibt es erst wenige Programme, die überhaupt MiNT-Treiber ansprechen können.

Ein Grund für die zögerliche Annahme dieses Konzeptes ist sicherlich, daß derzeit kaum Dokumentation vorhanden ist. Deshalb zeigen wir im ersten Teil einen Überblick über das Treiber-Konzept von MiNT, sowie ein erstes Beispiel-Programm, das die Verwendung der MiNT-Devices illustriert. Im zweiten Teil geht es um spezielle Terminal-Devices und in den weiteren Folgen geben wir Ihnen ein Beispiel für die Programmierung eines MiNT-Treibers.

Die Einbindung der Hardware in MiNT erfolgt grundsätzlich auf zwei Ebenen. Auf der oberen Ebene befinden sich die sogenannten Filesystems. Sie binden die Dateien in die Ordnerhierarchie des Betriebssystems ein, sind also für alles zuständig, was mit der Verwaltung von Verzeichnissen zu tun hat: z. B. das Durchsuchen von Verzeichnissen nach Dateinamen, Anlegen von neuen Dateien, Verwaltung von Dateiattributen und Zugriffsrechten u. ä. Man findet die Filesystems als Ordner im Wurzelverzeichnis des Pseudolaufwerks »U:« wieder. Das sind zum einen die Filesystems der angeschlossenen Speichermedien (die als Namen den jeweiligen Laufwerksbuchstaben tragen), aber auch spezielle Filesystems, durch die FiFos (»PIPE«), Adreßräume der laufenden Prozesse (»PROC«), Blöcke von Shared-Memory (»SHM«) oder zeichenorientierte Devices (»DEV«) unter Namen zugänglich sind. Unter der Ebene der Filesysteme liegen die Device-Treiber, die für die eigentliche Ein- und Ausgabe zuständig sind: Öffnen und Schließen von Dateien, Lese-/Schreiboperationen und ggf. weitere gerätespezifische Aufgaben.

Grundsätzlich erfolgt jeder Zugriff auf einen Device-Treiber über ein Filesystem. Wenn beispielsweise eine Datei geöffnet werden soll, durchsucht zunächst das Filesystem seine internen Verwaltungsstrukturen (das können je nach Filesystem z.B. die Directory-Sektoren einer Disk oder auch nur eine interne Liste im RAM sein) nach dem Dateinamen. Wird er gefunden, stellt das Filesystem fest, welcher Device-Treiber für diese Datei zuständig ist und ruft ihn zur Durchführung der eigentlichen Operation auf.

Welche physikalischen Daten sich hinter der Datei verbergen, ist ausschließlich Sache des Treibers. Das kann eine Datei auf der Platte sein, aber z.B. auch der Adreßraum eines MiNT-Prozesses (in den Pseudodateien des Filesystems »U:\PROC«) oder eben, an dieser Stelle interessant, die Daten, die eine Schnittstelle des Rechners oder sonstige spezielle Hardware liefert.

Offensichtlich gibt es einen engen Zusammenhang zwischen einem Treiber und dem dazugehörigen Filesystem. Die Treiber von blockorientierten Speichermedien sind meist integraler Bestandteil eines Filesystems und nach außen gar nicht sichtbar. Um aber einzelne Treiber für Geräte einbinden zu können, die kein eigenes Filesystem unterstützen (man spricht hier auch von zeichenorientierten Geräten oder Character-Devices), stellt MiNT das sogenannte BIOS-Filesystem zur Verfügung, das als »U:\DEV« erscheint. Das ist ein spezielles Filesystem, das quasi als Behälter für die einzelnen Devices dient. Diese werden dort als Pseudodateien dem Dateisystem zugänglich gemacht. Intern besteht dieses Filesystem nur aus einer Liste der Namen, unter dem die Devices eingebunden sind, sowie Verweisen auf die dazugehörigen Treiber.

MiNT installiert dort beim Booten Treiber für die Standardschnittstellen des Rechners, z.B. Konsole, Drucker und die seriellen Schnittstellen (die Treiber der derzeitigen Beta-Versionen von MiNT haben einige Mängel, auf die wir noch zurückkommen werden). Durch einen speziellen Aufruf an das BIOS-Filesystem kann man auch nachträglich neue Treiber eintragen lassen.

Durch die Einbindung der Gerätetreiber in das Dateisystem ergibt sich schon, wie sie angesprochen werden: Der Zugriff erfolgt im wesentlichen ganz einfach über normale GEMDOS-Funktionen. Mit Fopen() erhält man ein Handle für das gewünschte Gerät, mit Fread() und Fwrite() kann gelesen und geschrieben werden.

MiNT bietet neben der Möglichkeit, beliebige neue Treiber zu installieren erweiterte Zugriffsmöglichkeiten durch einige neue GEMDOS-Funktionen. Die wichtigste ist Fcntl(). Dieser Aufruf ist sozusagen ein Hintertürchen, Über das ein Treiber dem aufrufenden Programm beliebige neue Funktionen zur Verfügung stellen kann, die nicht in das Schema der normalen GEMDOS-Aufrufe passen (meist Kontrollfunktionen, mit denen das Verhalten des Treibers oder besondere Charakteristika des Geräts beeinflußt werden können). Mit einigen Ausnahmen gibt MiNT Fcntl-Aufrufe einfach unverändert an den Treiber weiter, und es ist diesem ganz allein überlassen, was sie bewirken. Die Parameter von Fcntl() sind so definiert:

	LONG Fcntl(WORD handle, LONG arg, WORD opcode);

»handle« ist dabei der GEMDOS-Handle des gewünschten Datei, wie er von Fopen() zurückgegeben wurde. Aus diesem Handle bestimmt MiNT auch, welcher Treiber angesprochen werden soll. »opcode« ist eine Nummer, die angibt, welche Funktion ausgeführt werden soll und »arg« ist ein Argument, dessen Bedeutung von der angewählten Funktion abhängt. Wie bereits erwähnt, ist es im Prinzip Sache des Treibers, welcher Opcode was bewirkt. Allerdings sind einige grundlegende Funktionen, die viele Treiber gemeinsam haben, schon festgelegt. Manche davon werden unabhängig vom Treiber von MiNT selbst behandelt. Andere sind per Konvention auf eine Bedeutung festgelegt, damit alle Treiber einheitlich aufgerufen werden können. Eine vollständige Auflistung der vorbelegten Nummern findet man in der MiNT-Dokumentation, in der Man-Page zu Fcntl (Handle- und Terminalorientierte Funktionen), sowie in der Datei DEVICE.DOC bzw. MODEM.DOC (Funktionen für serielle Schnittstellen). Normalerweise verwendet man nicht direkt die Opcode-Nummern, sondern symbolische Namen. Die Namen für die Standard-Opcodes sind bei MiNT in der Header-Datei FILESYS.H definiert. Auf einige Funktionen werden wir noch näher eingehen. Die Vorgehensweise zum Ansprechen eines Character-Devices läßt sich am besten an einem Beispiel illustrieren. Ein typisches Beispiel für ein Programm, das direkt auf MiNT-Devices zugreift, ist ein Terminalprogramm. Dessen wesentliche Aufgabe ist es, auf der Konsole eingetippte Zeichen auf ein Character-Device (meist eine serielle Schnittstelle) auszugeben, sowie von dort eingehende Zeichen wiederum auf der Konsole anzuzeigen. Unser in »C« geschriebenes Minimal-Terminalprogramm unter Benutzung von MiNT-Devices zeigt das Prinzip (Listing).

Der Programmablauf ist einfach. Zunächst sind lediglich einige Fcntl-Aufrufe nötig, um das Device zu konfigurieren. Schnittstellen, an die typischerweise Terminals zur Texteingabe angeschlossen werden, gehören zur Klasse der Terminal-Devices. Dazu zählen z.B. die Konsole und die seriellen Schnittstellen, nicht aber auf Binärdaten ausgelegte Devices wie z.B. der Drucker oder das Null-Device. Normalerweise interpretiert MiNT bei Terminal-Devices einige Zeichen als Kontrollzeichen, um dann Zeichenumwandlungen oder Kontrollfunktionen durchzuführen. Beispielsweise wird, wenn ein »Ctrl-C« über das Terminal eingegeben wird, ein Unterbrechungssignal an die Prozesse geschickt, die über dieses Terminal ausgeben. Das Ctrl-C selbst wird nicht weitergeleitet.

Dieses Verhalten wäre in unserem Fall höchst unerwünscht, denn ein Terminalprogramm sollte selbstverständlich auch die Übertragung von Ctrl-C erlauben, ohne daß es dabei beendet wird. Es sollen also alle Zeichen unbeeinflußt übertragen werden, ohne daß irgendwelche Nebeneffekte auftreten. Die Interpretation von Kontrollzeichen wird über einige Flags gesteuert, die mit dem Fcntl-Aufruf »TIOCSETP« für jedes Terminal-Device gesetzt werden können. Hier setzt man das Flag »T_RAW« und löscht alle anderen, was dazu führt, daß keinerlei Kontrollzeichen-Interpretation seitens MiNT stattfindet. Die genaue Vorgehensweise ist in der Funktion »set_cflags()« ab Zeile 128 zu sehen. Zunächst holen wir mit dem Fcntl-Aufruf »TIOCGETP« die derzeitige Einstellung. TIOCGETP erhält als Parameter einen Zeiger auf eine Struktur »sgttyb« (definiert in »FILESYS.H«), in die MiNT dann die Terminal-Einstellungen einträgt. Uns interessieren hier nur einige Bits im Feld sg_flags (maskiert mit »TC_FLAGS«, siehe Zeile 30). Diese werden zunächst ausmaskiert und dann die neuen Flags gesetzt. Zu guterletzt wird die Einstellung mit unseren neuen Flags mit TIOCSETP zurückgeschrieben.

Mit »set_cflag()« werden sowohl das Terminal-Device, als auch die Standardausgabe (Handle 1) in den T_RAW-Modus versetzt (Zeilen 80, 81). Die weiteren Fcntl-Aufrufe dienen zur Einstellung serieller Schnittstellen. Falls das Terminal keine solche Schnittstelle ist (und damit auch die Einstellung nicht unterstützt), ist das nicht schlimm: Die Fcntl-Aufrufe werden vom Device-Treiber als ungültig erkannt und ignoriert. Ausführliche Informationen zu den Terminal-Devices erhalten Sie im nächsten Teil unseres Kurses.

Auch die Opcodes zur seriellen Konfiguration werden wir in der nächsten Folge noch genau erläutern, an dieser Stelle nur ganz kurz: Mit »TIOCIBAUD« und »TIOCOBAUD« wird in den Zeilen 71 und 72 die Baudrate eingestellt, falls es sich bei dem Device um eine serielle Schnittstelle handelt.

Ungültige Baudraten werden im Beispiel nicht abgefangen. Mit dem Opcode »TIOCSFLAGS« können bei seriellen Schnittstellen die Übertragungsparameter eingestellt werden, also z.B. Anzahl der Datenbits, Parität, Handshake-Protokoll u.ä.. Der Einfachheit halber legen wir im Beispiel die Parameter auf das gebräuchliche Format »8N1« ohne Handshake fest (Zeile 77); in einem »richtigen« Terminalprogramm würde man das natürlich konfigurierbar machen. Beachten Sie, daß sämtliche Flags vor der Neueinstellung gesichert werden (Zeilen 71, 72, 76, 80, 81), damit vor Programmende der ursprüngliche Zustand wiederhergestellt werden kann. Das aufrufende Programm könnte sonst sehr verwirrt werden, denn die Terminaleinstellungen sind global wirksam, würden also auch nach Beendigung unseres Programms erhalten bleiben. Man sollte daher sorgfältig darauf achten, immer alles in demselben Zustand zu hinterlassen, wie man ihn vorher angetroffen hat.

Damit die Terminal-Einstellungen auch wirksam werden, ist es wichtig, das Programm mit dem Aufruf »Pdomain(1)« in die MiNT-Domain zu bringen (Zeile 48). Bei Programmen, die im TOS-Kompatibilitätsmodus laufen, werden sie nämlich von MiNT nicht beachtet.

Nun kann mit der eigentlichen Ein- und Ausgabe begonnen werden. Diese findet in der for-Schleife ab Zeile 88 statt. In der Endlosschleife werden eintreffende Zeichen vom Terminal auf die Standardausgabe sowie von der Standardeingabe auf das Terminal übertragen, bis der Benutzer durch die Undo-Taste abbricht. Hierbei werden wieder einige MiNT-Aufrufe benutzt. Diese Funktionen sind nicht auf zeichenorientierte Geräte beschränkt, sondern können auf beliebige Dateien angewandt werden. Dennoch sind sie gerade für die effiziente Benutzung der Character-Devices wichtig. Fselect() ist eine leistungsfähige Funktion, mit der mehrere Kanäle gleichzeitig auf Ein- und Ausgabebereitschaft überprüft werden können. Die Funktion ist so definiert: WORD Fselect (WORD timeout, LONG *rfds, LONG *wfds, LONG reserved);

(der letzte Parameter ist reserviert und sollte immer 0 sein). Über die LONG-Variablen, auf die »rfds« bzw. »wfds« zeigen, werden Fselect() zwei Bitmasken übergeben, mit denen man MiNT mitteilt, an welchen Kanälen man interessiert ist. Die Kanäle werden dabei durch Setzen des Bits markiert, dessen Nummer dem Filehandle des Kanals entspricht (für die Standardeingabe mit Handle 0 würde man also Bit 0 setzen, für ein File mit dem Handle 6 das Bit 6 usw.). Da MiNT maximal 32 offene Filehandles pro Prozeß erlaubt, reicht ein LONG für alle möglichen Handles aus (mit Ausnahme der speziellen systemglobalen Handles, dafür müßte man vorher mittels Fdup() einen lokalen Handle erzeugen). Es können beliebig viele der 32 möglichen Bits gesetzt sein, zu beachten ist nur, daß alle einem gültigen Filehandle entsprechen, sonst bricht Fselect() mit einem Fehler ab. Die durch »rfds« angegebenen Kanäle werden auf Eingabebereitschaft geprüft (also ob dort Zeichen zum Abholen bereitstehen), und die von »wfds« auf Ausgabebereitschaft. Für jeden der beiden Parameter kann auch ein NULL-Zeiger übergeben werden, dann wird er nicht beachtet. Stellt Fselect() auf einem oder mehreren Kanälen Übertragungsbereitschaft fest, kehrt es zurück und liefert dabei die Anzahl der Kanäle zurück, de bereit sind. Welche Kanäle das im einzelnen sind, kann nun über die LONG-Variablen festgestellt werden, auf die »rfds« und »wfds« zeigen. Dort werden die entsprechenden Bits auf 1 gesetzt, die aller anderen Kanäle auf 0. Durch den Parameter »timeout« kann man das Verhalten von Fselect() in dem Fall bestimmen, daß beim Aufruf keiner der gewählten Kanäle bereit ist. Falls »timeout« einen positiven Wert hat, wartet Fselect() mindestens diese Anzahl von Millisekunden ab, bevor es zurückkehrt. Solange bleibt der aufrufende Prozeß im Schlafzustand und beansprucht dabei kaum Rechenzeit, was in einem Multitasking-System sehr günstig ist.

Wenn in dieser Zeit keiner der Kanäle Übertragungsbereitschaft signalisiert, kehrt Fselect() schließlich nach Ablauf des Timout zurück (als Anzahl der bereiten Kanäle wird dann 0 zurückgeliefert, und alle Bits von »rfds« und »wfds« sind gelöscht). Wenn dagegen während der Wartezeit Bereitschaft auf einem Kanal auftritt, kehrt Fselect() sofort zurück, der Aufrufer kann weiterlaufen und die I/O bearbeiten - zumindest in der Theorie. In der Praxis funktioniert das bei den bisherigen MiNT-Versionen leider nur mit Kanälen, die auf FiFos (Pipes) verweisen, sowie mit der Konsole (Tastatureingaben). Hier liegt einer der Mängel der in MiNT eingebauten Device-Treiber. Sie sind einfach nicht in der Lage, Fselect() »aufzuwecken«, wenn eine Übertragung möglich wird. Ist beim Aufruf von Fselect() kein Kanal bereit, kehrt Fselect() erst zurück, wenn der Timeout abgelaufen ist, egal ob inzwischen Daten eintreffen oder nicht. Bleibt zu hoffen, daß MiNT bald neue Device-Treiber bekommt. Bis dahin ist nur möglich, Fselect() pollend, also mit kurzem Timeout, zu benutzen, damit es nicht längere Zeit hängenbleibt. Das bedeutet leider wieder höhere Rechenzeit. In unserem Beispiel wird der minimale Timeout von 1 Millisekunde verwendet (Zeile 91). Wir wollen abfragen, ob der Benutzer inzwischen etwas eingetippt hat oder neue Zeichen auf dem Terminal eingetroffen sind, d.h. ob Lesebereitschaft auf der Standardeingabe oder auf dem Filehandle des Terminals vorliegt. In Zeile 86 wird mit dem Ausdruck (1L < handle) das Bit für den Terminal-Handle gesetzt, für die Standardeingabe ist es immer Bit 0 (also Binärwert 1). In »read_maske« werden diese beiden Bits für Fselect() gesetzt.

Nach der Rückkehr von Fselect() prüfen wir, auf welchen Kanälen Lesebereitschaft vorliegt. Ist es das Terminal, versuchen wir, soviele Bytes, wie in den Lesepuffer passen, mithilfe der TOS-Funktion Fread() einzulesen (Zeile 95). Beachten Sie, daß der Fread() garantiert sofort zurückkehrt, denn durch Fselect() wissen wir, daß mindestens ein Zeichen gelesen werden kann, also Fread() nicht blockiert. Auch wenn nicht soviele Bytes vorliegen, wie in den Puffer passen würden, liest es die vorhandenen ein und kehrt sofort zurück. Wenn es auf Geschwindigkeit ankommt (wie es bei hohen Baudraten der Fall ist), ist es auf jeden Fall eine günstige Strategie, möglichst viele Bytes auf einmal einzulesen. Unter MiNT ist jeder Betriebssystemaufruf »teuer«, da mit einem recht hohen Verwaltungs-Overhead verbunden. Die Bytes einzeln einzulesen ist ineffizient und kann die Übertragungsrate deutlich vermindern.

Als nächstes schreiben wir alle eingelesenen Bytes mit Fwrite() auf die Standardausgabe zurück. Die Anzahl der Bytes ist dem Rückgabewert des vorherigen Fread() zu entnehmen. Auch hier gilt wieder: Am besten viele Zeichen auf einmal schreiben.

Bei Zeichen von der Konsole geht man anders vor: Zum einen, weil hier die Geschwindigkeit nicht kritisch ist, zum anderen, weil noch abgefragt werden muß, ob die Abbruchtaste gedrückt wurde. Es muß also jedes einzelne Zeichen geprüft werden. Zudem wollen wir jedes beliebige ASCII-Zeichen über das Terminalprogramm übertragen können, daher kommt als Abbruchtaste nur eine der Sondertasten des Atari in Frage (hier UNDO). Diese Tasten haben jedoch keinen ASCII-Code, sondern sind nur über den Scancode zu identifizieren. Fread() kommt also nicht in Frage, da es keinen Scancode liefert. Für solche Zwecke gibt es die MiNT-Funktion Fgetchar():

	LONG Fgetchar (WORD handle, WORD mode);

Diese Funktion liest nur ein Zeichen ein. Falls von einem Terminal gelesen wird, das Scancodes liefert (z.B. die Konsole), wird dieser auch zurückgeliefert, und zwar in derselben Kodierung wie bei der BIOS-Funktion Bconin(). In den Bits 16 bis 23 des LONG steht der Scancode, Bits 24 bis 31 enthalten den Kbshift-Status. Hiermit können also auch Sondertasten wie UNDO identifiziert werden. Zusätzlich kann man in »mode« festlegen, ob Fgetchar() Kontrollzeichen interpretiert. Das hat eine ähnliche Funktion, wie die Terminal-Flags für Fread()/Fwrite(). Hier wollen wir wieder im Raw-Modus ohne Kontrollzeichen-Interpretation lesen, was durch den Wert 0 für »mode« bewirkt wird. Falls UNDO gedrückt oder ein »End-Of-File«-Zustand erreicht wurde (angezeigt durch den speziellen Rückgabewert MINTEOF), wird die Hauptschleife abgebrochen. Andernfalls schreiben wir das eingetippte Zeichen mit dem schreibenden Gegenstück zu »Fgetchar()«, der Funktion »Fputchar()«, auf das Terminal. Diese funktioniert ganz äquivalent zu Fgetchar(), allerdings hat der Rückgabewert natürlich eine andere Bedeutung (bei fehlerfreier Übertragung die Anzahl der geschriebenen Bytes). Am Programmende werden dann die Terminal-Flags und die Baudrate restauriert (ab Zeile 114) und das Terminal-File geschlossen. Das Programm ist nicht »großartig«, aber durchaus geeignet, um z.B. ein Modem direkt anzusprechen oder einen Zweit-ST als Remote-Terminal zu verwenden. Dazu kann man die beiden Rechner z.B. über ein Nullmodem-Kabel oder MIDI-Kabel verbinden. Auf dem Zweit-Rechner startet man dann das Mini-Terminal mit dem gewünschten Device. Nun kann man auf dem Hauptrechner z.B. eine Shell mit Umlenkung auf diese Schnittstelle starten und vom Zweitrechner aus bedienen.

Die Verwendung der MiNT-Devices hat gegenüber den althergebrachten BIOS-Funktionen den Vorteil der Device-Unabhängigkeit. Das Terminalprogramm läuft ohne jede Änderung mit allen seriellen Schnittstellen, man muß nur das gewünschte Device auf der Kommandozeile angeben. Auch z.B. die MIDI-Schnittstelle kann ohne Probleme bedient werden (Device »u:\ dev\midi«).

Ein weiterer Nachteil der BIOS-Funktionen, unter dem besonders Terminalprogramme leiden, wird bei den MiNT-Treibern in naher Zukunft gelöst sein. Bestimmte Aufgaben, wie z.B. das Abfragen der Carrier-Detect-Leitung an der seriellen Schnittstelle, erfordern bisher noch direkten Zugriff auf die Hardware mit dem Erfolg, daß es auf neuen Rechnern möglicherweise nicht funktioniert. Unter MiNT werden einfach neue Fcntl-Opcodes für diese Aufgaben definiert und der Treiber kümmert sich um die Hardware. Bei einer Änderung muß nur ein neuer Treiber installiert werden und Anwendungsprogramme können ohne Änderung damit laufen. (uw)

Tips für Programmierer Welches Device von einem Programm benutzt wird, sollte immer vom Benutzer konfigurierbar sein (z.B. über die Kommandozeile, wie im Beispiel).

Listing: Mini-Terminal für MiNT

/* miniterm.c            Stephan Baucke, 12/1991
 *
 * Benutzung: miniterm [device [baudrate]]
 *
 * Minimal-Terminalprogramm unter Benutzung von MiNT-Gerätetreibern.
 * Auf der Kommandozeile können optional der Name des gewünschten
 * MiNT-Devices (Beispiel: »U:\DEV\MODEM1«) sowie die Baudrate angegeben
 * werden. Falls kein Device angegeben wird, wird MODEM1 verwendet.
 * Fehlt die Baudrate, wird die alte Einstellung übernommen.
 *
 * Compiler: Pure C
 */

#include <stdlib.h>
#include <tos.h>
#include <mintbind.h>
#include <filesys.h>

#ifndef TIOCGFLAGS		/* Ist in manchen FILESYS.H nicht definiert */
	#define TIOCGFLAGS   ((‚T‘ << 8) | 22)	/* get terminal flags */
	#define TIOCSFLAGS   ((‚T‘ << 8) | 23)	/* set terminal flags */
	#define TIOCIBAUD    ((‚T‘ << 8) | 18)	/* set/get input baudrate */
	#define TIOCOBAUD    ((‚T‘ << 8) | 19)	/* set/get output baudrate */
#endif

#define DEFAULT_DEVICE   "U:\\DEV\\MODEM1"	/* Default-Terminal */
#define ABORT_KEY        0x61000OL	/* Scancode der Abbruchtaste (UNDO) */
	/* Maske für relevante Terminal-Control-Flags */
#define TC_FLAGS (T_CRMOD | T_CBREAK | T_ECHO | T_RAW | T_TOS | T_TOSTOP | T_XKEY)

int isatty (int handle)	/* Prototypen */
short set_cflags (int handle, short flags);

char buf[8192];			/* Lese-Puffer */

/*
   Hauptroutine: Intialisierung und Terminal-Hauptschleife
*/
int main (int argc, char **argv)
{
	long  l;
	int   handle;		/* Handle des Terminals */
	long  read_mask, term_mask;	/* Masken für Fselect() */
	short t_cflags, o_cflags, rs_flags;	/* Speicher für alte Flags */
	long  ibaud, obaud;	/* Speicher für alte Baudraten */

	Pdomain(1);			/* MiNT-Domain setzen */

	/*** Device öffnen ***/
	l = (Fopen((argc > 1)? argv[1] : DEFAULT_DEVICE, O_RDWR);

	if (l < 0)			/* Fehler beim Öffnen? */
	{
		Cconws ("Kann");
		Cconws (argv[1];);
		Cconws ("nicht öffnen\r\n");
		return 1;
	}

	handle = (int) l;
	if (!isatty (handle))	/* Gar kein Terminal? */
	{
		Cconws (arg[1]);
		Cconws ("ist kein Terminal\r\n");
		return 2;
	}

	/*** Terminal-Flags setzen, dabei alte Einstellung merken ***/

	ibaud = 0;
	if (argc == 3 && (ibaud = atol(argv[2])) > 0 )	/* Baudraten setzen */
	{
		obaud = ibaud;
		Fcntl (handle, (long)&ibaud, TIOCIBAUD);
		Fcntl (handle, (long)&obaud, TIOCOBAUD);
	}

	rs_flags = 0;		/* serielle Parameter auf 8N1 stellen */
	Fcntl (handle, (long)&rs_flags, TIOCGFLAGS);	/* alte Flags merken */
	t_cflags = 0x0001;
	Fcntl (handle, (long)&t_cflags, TIOCSFLAGS);

	t_cflags = set_cflags (handle, T_RAW);	/* Terminal auf RAW */
	o_cflags = set_cflags (1, T_RAW);	/* stdaut auf RAW */

	/* *** Terminal Haupt-Schleife *** */

	Cconws ("*** Terminal gestartet (Abbruch mit UNDO)\r\n\r\n);
	term_mask = (1L << handle);	/* Bitmaske für Fselect */

	for (;;)
	{
		read_mask = 1L | term_mask;	/* Daten auf stdin oder Terminal? */
		if (Fselect (1 , &read_mask, (long *)0, OL) > 0)
		{			if (read_mask & term_mask)	/* Zeichen vom Terminal? */
			{
				l = Fread (handle, sizeof (buf), buf);
				if (l > 0)
					Fwrite (1, l, buf);	/* Auf stdout ausgeben */
			}

			if (read_mask & 1)	/* Zeichen von stdin? */
			{
				l = Fgetchar (0, 0);
				if (l < 0)
					break;
				if (l == MINTEOF || (1 & 0x0ffffffL) == ABORT_KEY)
					break;	/* UNDO der EOF */
				if (Fputchar(handle, 1, 0) < 0)	/* Auf Terminal ausgeben */
					break;
			}
		}					/* if (Fselect() > 0) */
	}						/* for (;;) */

	/* *** Aufräumen *** */

	if (rs_flags != 0)	/* wenn alte Flags gesichert, restaurieren */
		Fcntl (handle, (long)&rs_flag , TIOCSFLAGS);
	if (ibaud > 0)			/* wenn alte Baudraten gesichert, zurück */
	{
		Fcntl (handle, (long) &ibaud, TIOCIBAUD);
		Fcntl (handle, (long) &obaud, TIOCOBAUD);
	}

	set_cflags (handle, t_cflags);	/* Kontroll-Flags restaurieren */
	set_cflags (1, o_cflags);

	Fclose (handle);		/* Terminal schließen */
	return 0;				/* fini */
}

/*
 * Setzt die Terminal-Kontroll-Flags, falls ‚flags‘ >= 0 ist. Liefert
 * die vorher gesetzten Flags zurück oder -1, falls die Flags nicht
 * beeinflußt werden konnten (kein Terminal-Device).
*/

short set_cflags (int handle, short flags)
{
	struct sgttyb tty;
	short old_flags = -1;

	if (flags >= 0)		/* Gültige Flags? */
	{
		if (Fcntl (handle, (long)&tty, TIOCGETP) >= 0)	/* alte holen */
		{
			old_flags = tty.sg_flags & TC_FLAGS;
			tty.sg_flags = (tty.sg_flags & ~TC_FLAGS) | flags;
			Fcntl (handle, (long)&tty, TIOCSETP);	/* neue setzen */
		}
	}
	return old_flags;
}

Stephan Baucke
Aus: ST-Magazin 08 / 1993, Seite 54

Links

Copyright-Bestimmungen: siehe Über diese Seite