PCI-BIOS (4)

Memory- und I/O-Bereiche

Sämtliche Routinen, die für die Zugriffe auf Memory- und I/O-Bereich zuständig sind, berücksichtigen dabei auch das entsprechende Speicherformat (d.h. Intel/Motorola) der vorliegenden Resource. Für den Aufrufer dieser Funktion sieht es dann so aus, als würde es sich immer um das hauseigene (Motorola-) Format handeln.

Ein Treiber kann also diese Funktionen benutzen, um auf Register bzw. kleinere Speicherbereiche zuzugreifen. Für größere Speicherbereiche kann es unter Umständen günstiger sein, unter Berücksichtigung der mittels get_resource zur Verfügung gestellten Informationen, eigene Kopierroutinen zu schreiben.

Prinzipiell kann es ja auch mehrere voneinander unabhängige Memory- bzw. I/O-Bereiche geben. Was soll denn dies nun wieder heißen? Tja, erstens müssen z.B. die Memory-Bereiche mehrerer PCI-Geräte im Speicher nicht unbedingt hintereinander zu liegen kommen, und zweitens können in der betroffenen Hardware auch zwei voneinander physikalisch getrennte PCI-Busse vorliegen. Die Berücksichtigung dieser Umstände ist aber Aufgabe eines PCI-BIOS und der Anwender braucht sich darüber überhaupt keine Sorgen zu machen. Um jetzt aber doch ein wenig Verwirrung zu stiften, möchte ich noch erwähnen, dass ein PCI-Gerät ja auch mehrere typgleiche Resourcen (Memory und/oder I/O) belegen kann. Und diese Gegebenheit kann vom PCI-BIOS leider nicht selbständig berücksichtigt werden. Wie sollte das PCI-BIOS auch selbst "erahnen", welchen von mehreren möglichen Bereichen man nun ansprechen möchte. Die Auswahl der entsprechenden Resource ist somit in der in den Routinen verwendeten Zugriffsadresse miteinzubeziehen.

Hier hilft uns dann wohl nur noch ein Beispiel weiter: Belegt ein PCI-Gerät z.B. zwei I/O-Bereiche, sind deren Startadressen über get_resource zu ermitteln (nennen wir sie Startadresse1 und Startadresse2). Wollen wir nun ein Register im zweiten I/O-Bereich ansprechen, so addieren wir zur gewünschten Registeradresse (Offset vom Beginn des betroffenen I/O-Bereichs) die Startadresse2 und erhalten somit die für die Routinen notwendige Zugriffsadresse. Selbige Vorgangsweise ist auch notwendig, wenn nur eine typgleiche Resource vorhanden ist. In diesem Fall wäre eben immer Startadresse1 zur gewünschten Registeradresse zu addieren.

Memory Read

Diese drei Funktionen erlauben also Lesezugriffe auf den Memory-Bereich unter Berücksichtigung des vorliegenden Speicherformats (d.h. Intel/Motorola).

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.L Zugriffsadresse (Offset innerhalb des Memory-Adreßbereichs)
A0.L Zeiger auf Ergebnisvariable
Ausgang:
D0.L PCI-BIOS Fehlercode
gelesene Daten stehen nach dem Aufruf in der Ergebnisvariable

Aufruf in C:
LONG errorcode = read_mem_byte (LONG handle, ULONG offset, UBYTE *adresse)
LONG errorcode = read_mem_word (LONG handle, ULONG offset, UWORD *adresse)
LONG errorcode = read_mem_longword (LONG handle, ULONG offset, ULONG *adresse)
handle: Geräte-Handle des gewünschten PCI-Gerätes
offset: Zugriffsadresse (Offset innerhalb des Memory-Adreßbereichs)
adresse: Zeiger auf Ergebnisvariable
Returnwert: PCI-BIOS Fehlercode

FAST Memory Read

Diese drei Funktionen erlauben das Lesen von der angegebenen Memory-Adresse ohne aufwendige Fehler- und Plausibilitätschecks und sind daher die entsprechenden Pendants zu den "schnellen" Routinen für den Konfigurationsbereich. Der Registerinhalt wird wieder im Returnwert abgelegt und daher können diese Funktionen in C-Programmen ohne viel "Overhead" angewendet werden.

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.L Zugriffsadresse (Offset innerhalb des Memory-Adreßbereichs)
Ausgang:
D0 gelesener Wert (8, 16 oder 32 Bits)

Aufruf in C:
UBYTE value = fast_read_mem_byte (LONG handle, ULONG offset)
UWORD value = fast_read_mem_word (LONG handle, ULONG offset)
ULONG value = fast_read_mem_longword (LONG handle, ULONG offset)

handle: Geräte-Handle des gewünschten PCI-Gerätes
offset: Zugriffsadresse (Offset innerhalb des Memory-Adreßbereichs)
Returnwert: gelesener Wert (8, 16 oder 32 Bits)

Memory Write

Als Gegenstück zu obigen Leseroutinen schreiben diese drei Routinen den gewünschten Wert an die angegebene Memory-Adresse.

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.L Zugriffsadresse (Offset innerhalb des Memory-Adreßbereichs)
D2 zu schreibender Wert (8/16/32 Bits)
Ausgang:
D0.L PCI-BIOS Fehlercode

Aufruf in C:
LONG errorcode = write_mem_byte (LONG handle, ULONG offset, UBYTE val)
LONG errorcode = write_mem_word (LONG handle, ULONG offset, UWORD val)
LONG errorcode = write_mem_longword (LONG handle, ULONG offset, ULONG val)

handle: Geräte-Handle des gewünschten PCI-Gerätes
offset: Zugriffsadresse (Offset innerhalb des Memory-Adreßbereichs)
val: zu schreibender Wert (8/16/32 Bits)
Returnwert: PCI-BIOS Fehlercode

IO Read

Und da es auch einen I/O-Bereich gibt, sind im PCI-BIOS auch hierfür die entsprechenden Routinen vorhanden. Der Aufbau der Routinen selbst ist mit denen des Memory-Bereichs identisch.

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.L Zugriffsadresse (Offset innerhalb des I/O-Adreßbereichs)
A0.L Zeiger auf Ergebnisvariable
Ausgang:
D0.L PCI-BIOS Fehlercode
gelesene Daten stehen nach dem Aufruf in der Ergebnisvariable

Aufruf in C:
LONG errorcode = read_io_byte (LONG handle, ULONG offset, UBYTE *adresse)
LONG errorcode = read_io_word (LONG handle, ULONG offset, UWORD *adresse)
LONG errorcode = read_io_longword (LONG handle, ULONG offset, ULONG *adresse)

handle: Geräte-Handle des gewünschten PCI-Gerätes
offset: Zugriffsadresse (Offset innerhalb des I/O-Adreßbereichs)
adresse: Zeiger auf Ergebnisvariable
Returnwert: PCI-BIOS Fehlercode

FAST IO Read

Diese drei Funktionen erlauben wieder das Lesen von der angegebenen I/O-Adresse ohne aufwendige Fehler- und Plausibilitätschecks. Die Registerinhalte werden wie gehabt im Returnwert abgelegt und daher sind diese Routinen wieder ideal in C-Programmen einsetzbar.

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.L Zugriffsadresse (Offset innerhalb des I/O-Adreßbereichs)
Ausgang:
D0.L gelesener Wert (8, 16 oder 32 Bits)

Aufruf in C:
UBYTE value = fast_read_io_byte (LONG handle, ULONG offset)
UWORD value = fast_read_io_word (LONG handle, ULONG offset)
ULONG value = fast_read_io_longword (LONG handle, ULONG offset)

handle: Geräte-Handle des gewünschten PCI-Gerätes
offset: Zugriffsadresse (Offset innerhalb des I/O-Adreßbereichs)
Returnwert: gelesener Wert (8, 16 oder 32 Bits)

IO Write

Und zum Abschluß schließlich die Schreibroutinen für den I/O-Bereich, die den gewünschten Wert an die angegebene I/O-Adresse schreiben.

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.L Zugriffsadresse (Offset innerhalb des I/O-Adreßbereichs)
D2 zu schreibender Wert (8/16/32 bits)
Ausgang:
D0.L PCI-BIOS Fehlercode

Aufruf in C:
LONG errorcode = write_io_byte (LONG handle, ULONG offset, UBYTE val)
LONG errorcode = write_io_word (LONG handle, ULONG offset, UWORD val)
LONG errorcode = write_io_longword (LONG handle, ULONG offset, ULONG val)

handle: Geräte-Handle des gewünschten PCI-Gerätes
offset: Zugriffsadresse (Offset innerhalb des I/O-Adreßbereichs)
val: zu schreibender Wert (8/16/32 bits)
Returnwert: PCI-BIOS Fehlercode

Treibermanagement

Die folgenden Funktionen sind notwendig, um die installierten PCI-Gerätetreiber auf BIOS-Ebene verwalten zu können. Mit Hilfe dieser Routinen kann man nämlich feststellen, welche PCI-Geräte von welchen Treibern bedient werden. Die Gerätetreiber können wiederrum treibereigene Callback-Routinen eintragen, über die andere Treiber die "Kommunikation" aufnehmen können. Diese Callback-Routinen sind vor allem dann notwendig, wenn man einen Gerätetreiber durch einen anderen ersetzen möchte. Man teilt dem alten Treiber über die entsprechende Callback-Routine mit, daß er sich selbständig aus der Interruptkette "rückstandsfrei" entfernen soll. Und erst danach kann der neue Treiber das entsprechende PCI-Gerät in Beschlag nehmen und seinen eigenen Interrupthandler eintragen (siehe auch hook_interrupt und unhook_interrupt ).

Get Card Used

Mit dieser Routine kann man feststellen, ob das betreffende PCI-Gerät bereits von einem anderen Gerätetreiber bedient wird und ob bzw. wie dieser Treiber deinstalliert werden kann. Weiters liefert diese Funktion die Adresse der Callback-Routinen, wenn der Gerätetreiber diese zur Verfügung stellt (siehe Status).

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
A0.L Adresse des Zeigers auf die Callback-Routine des Treibers
Ausgang:
D0.L PCI-BIOS Fehlercode bzw. Status des PCI-Gerätes

Aufruf in C:
LONG status = get_card_used (LONG handle, ULONG *adresse)
handle: Geräte-Handle des gewünschten PCI-Gerätes
adresse: Zeiger auf Callback-Routine des Treibers
Returnwert: positiv - Status des PCI-Gerätes
negativ - PCI-BIOS Fehlercode

Set Card Used

Diese Routine dient nun endlich dazu, einen Gerätetreiber für das betreffende PCI-Gerät anzumelden. Dadurch ändert sich auch die mittels get_card_used ermittelbare Status-Information des PCI-Gerätes.

Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
A0.L Adresse der Callback-Routine (diesmal kein Zeiger auf Adresse !) bzw. Status (0/1/3)
Ausgang:
D0.L PCI-BIOS Fehlercode

Aufruf in C:
LONG error code = set_card_used (LONG handle, ULONG *callback)
handle: Geräte-Handle des gewünschten PCI-Gerätes
callback: Adresse der Callback-Routine bzw. Status
Returnwert: PCI-BIOS Fehlercode


Tabelle 2: Bedeutung des Parameters callback
callback Bedeutung
0 Gerät wird wieder freigegeben
1 Treiber für dieses Gerät kann nicht wieder deinstalliert werden (keine Callback-Routinen vorhanden)
3 Gerät kann von einem anderen Treiber ohne weitere Aktivitäten sofort übernommen werden
? jeder andere Wert von callback wird als Adresse der Callback-Routine interpretiert und der Status des betreffenden PCI-Gerätes wird auf 2 gesetzt

Callback-Routinen

Nachdem wir nun die wichtigsten Routinen des PCI-BIOS besprochen haben, ist es nun an der Zeit, auch diejenigen Routinen näher zu erläutern, die von den Gerätetreibern selbst zur Verfügung gestellt werden sollten. Sämtliche Callbackroutinen haben die gleiche Einsprungadresse, die ja über get_card_used ermittelt werden kann. Daß diese Funktionen nur diejenigen Register verändern dürfen, die zur Parameterübergabe selbst bzw. den Returnwert reserviert sind, ist vor allem für die Assembler-Programmierer interessant, und versteht sich wohl von selbst (oder etwa nicht? ;-) Die folgenden beiden Callback-Routinen müssen also von den Treibern unbedingt zur Verfügung gestellt werden. Anderenfalls kann man solche Gerätetreiber nur durch einen Griff zum Reset-Knopf entfernen. Es sei denn, der Treiber "hängt" in keinerlei Interrupts und belegt auch keine sonstigen Systemresourcen.

Callback 0: Get Driver ID

Diese Callback-Routine dient nun dazu, die ID des Gerätetreibers zu ermitteln, die der beim XBRA-Verfahren verwendeten ID entsprechen sollte. Dadurch ist der betreffende Treiber dann auch "namentlich" bekannt.

Aufruf in Assembler:
Eingang:
D0.L Funktionsnummer der Callback-Routine, d.h. 0
Ausgang:
D0.L ID des Gerätetreibers (=XBRA-ID)

Aufruf in C:
LONG id = callback (LONG function)
function: Funktionsnummer der Callback-Routine(0)
Returnwert: ID des Gerätetreibers (=XBRA-ID)

Callback 1: Try to remove driver

Mit Hilfe dieser Routine kann man nun (wie bereits mehrmals angesprochen) einen Treiber dazu veranlassen, sich zu deinstallieren. Dies ist immer dann unumgänglich, wenn das PCI-Gerät bereits von einem anderen Treiber bedient und mittels get_card_used der betreffende Status zurückgeliefert wird. Ob dies vom neu zu installierenden Gerätetreiber nun automatisch erledigt wird, oder dieser dem Anwender die Entscheidung eines Treiberwechsels überlässt, hängt dann vom Gerätetreiber selbst ab. Man kann ja schließlich dem Anwender auch den Namen des bereits installierten Treibers (Callback 0) mitteilen, und nachfragen, ob denn dieser durch den neuen Gerätetreiber ersetzt werden soll.

Aufruf in Assembler:
Eingang:
D0.L Funktionsnummer der Callback-Routine,d.h. 1
Ausgang:
D0.L 0 - Treiber konnte deinstalliert werden
1 - Treiber konnte nicht entfernt werden

Aufruf in C:
LONG result = callback (LONG function)
function: Funktionsnummer der Callback-Routine (1)
Returnwert: 0 - Treiber konnte deinstalliert werden
1 - Treiber konnte nicht entfernt werden

Installation des PCI-BIOS

Milan-Besitzer können die folgenden Absätze ruhigen Gewissens überspringen (sie zu lesen schadet aber auch nicht), denn der Milan hat das PCI-BIOS bereits im TOS integriert. Also, Einschalten und gleich Loslegen lautet hier die Devise.

Als Hades-Besitzer kopiert man PCI_BIOS.PRG aus dem Archiv einfach in den AUTO-Ordner, möglichst an den Beginn, aber auf jeden Fall bevor Gerätetreiber auf PCI-Karten zugreifen und dazu die BIOS-Schnittstelle ja schon benötigen. Das PCI-BIOS legt dazu einen _PCI Cookie an, über den dann die BIOS-Routinen lokalisiert und verwendet werden können. Näheres dazu steht auch in der ATARI PCI BIOS Specification (die aktuelle Ausgabe trägt die Versionsnummer V1.12). Am Hades besteht aber zusätzlich auch noch die Möglichkeit, ein Low-Level BIOS (PCI_CONF.PRG von Torsten Lang) zu installieren, das nach der Initialisierung der PCI-Geräte die notwendigen Informationen über Subcookies bereitstellt. Näheres zu PCI_CONF.PRG kann man beim Autor Torsten Lang erfragen.

Startet man also PCI_CONF.PRG vor PCI_BIOS.PRG, so liegt bereits ein _PCI Cookie vor. Das PCI-BIOS verwendet dann die bereits von PCI_CONF über die Subcookies bereitgestellten Informationen und steuert dann (nur) noch die Schnittstelle für die BIOS-Routinen bei. Auch das Interrupt-Handling wird dann vom PCI-BIOS an PCI_CONF weitergeleitet. Da es für den Milan ein PCI_CONF.PRG aber nicht geben wird, sollten die Treiber-Programmierer ausschließlich die standardisierte BIOS-Schnittstelle verwenden, was aber wiederrum die Verwendung von PCI_CONF nicht ausschließt. Gerätetreiber, die sich aber nur von den Subcookies des PCI_CONF "ernähren", werden auf anderen ATARI-kompatiblen PCI-Rechnern wie dem Milan nicht funktionieren.

Verwendet man PCI_BIOS.PRG alleine, so übernimmt es (mit Ausnahme der Bereitstellung der Subcookies) die Aufgaben von PCI_CONF.PRG und legt den _PCI Cookie mit den für die BIOS-Schnittstelle notwendigen Informationen selbst an. Die Informationen, die PCI_CONF normalerweise über Subcookies bereitstellen würde, sind aber nicht verloren, denn hierfür stellt das PCI-BIOS ja die standardisierte Routine get_resource zur Verfügung.



Aus: ST-Computer 10 / 1998, Seite 46

Links

Copyright-Bestimmungen: siehe Über diese Seite