Die Idee zum PCI-BIOS für die ATARI-kompatiblen Rechner entstand aus der Notwendigkeit der Zuordnung von Speicherbereichen zu den I/O- und Memory-Bereichen der PCI-Geräte.
Auf PCs und Kompatiblen wird die PCI-Funktionalität über eine BIOS-Erweiterung (das PCI-BIOS) standardisiert zur Verfügung gestellt. Das BIOS hat zwei wesentliche Aufgaben. Zum einen die Bereitstellung einer Schnittstelle für die Software für den Zugriff auf die PCI-Geräte, zum anderen die Initialisierung und die Verteilung der Resourcen der einzelnen PCI-Geräte. Die Initialisierung läuft ohne Eingriffsmöglichkeiten des Benutzers direkt nach dem Reset ab.
Nach dem Reset fragt das Hostsystem zuerst das Vorhandensein der installierten PCI-Geräte ab. Dies erfolgt durch den Zugriff auf ein definiertes Register (Vendor ID). Zu diesem Zeitpunkt sind Zugriffe auf die noch unbekannten Karten nur mit den Befehlen Configuration Read und Configuration Write möglich. Nachdem das System herausbekommen hat, ob und wo Karten im Rechner stecken, werden bei allen PCI-Geräten die benötigten Adreßräume ermittelt. Dies geschieht wie bereits erwähnt durch das Schreiben des Wertes FFFF'FFFFhex in das jeweilige Adreßregister. Jedes PCI-Gerät kann maximal 6 Speicherbereiche anfordern. Dabei wird der jeweils benötigte Adreßbereich im Adreßregister bitweise ausmaskiert. Werden zur Kodierung zum Beispiel Bit 0 bis Bit 9 verwendet (auskodieren mit FFFF'FC00hex), ist der Bereich 2^10, also 1024 Bytes groß. Es besteht die Wahl zwischen I/O-gemappten und Memory-gemappten Bereichen (siehe Artikel in Ausgabe 5/98). Für ein PCI-Gerät unterscheiden sich die beiden Bereiche nur durch die unterschiedlichen Kommandos an /C/BE[0..3].
Als Antwort teilt das Hostsystem (BIOS) den einzelnen Geräten jeweils Adreßbereiche zu - sofern im Adreßraum genug Platz vorhanden ist. Hier hat z.B. der Hades im Vergleich zum PC einiges mehr zu bieten, da im PC der I/O-Bereich sehr begrenzt ist. Der Hades bietet in unserem Fall 256 MB I/O-Bereich und 512 MB Memory-Bereich an. Das sollte für den Anfang wirklich genügen ;-) Nach der Anmeldung sind die PCI-Geräte dann selbst für die Auskodierung ihrer Adreßräume verantwortlich. Die zugeteilten Adreßräume lassen sich für die späteren Zugriffe auf I/O- und Memory-Bereich aus den Konfigurationsregistern der Geräte auch wieder auslesen. In einem Register des definierten Konfigurationsbereiches (Interrupt Pin) wird von jedem PCI-Gerät die jeweils benutzte Interrupt-Leitung mitgeteilt. Das BIOS vergibt daraufhin einen der vorhandenen Interrupts und stellt die weitere Funktionalität über BIOS-Routinen zur Verfügung.
Der Treiber einer installierten PCI-Hardware durchläuft demnach folgende Schritte für die Initialisierung und anschließende Verwendung eines PCI-Boards:
" Test auf vorhandenes PCI-BIOS und dessen Versionsnummer
" PCI-Karte mit den Routinen find_pci_device oder find_pci_classcode suchen; die Karte muss dann
ausschließlich über das vom PCI-BIOS vergebene Geräte-Handle angesprochen werden.
" entweder werden über get_resource die notwendigen Informationen für den direkten Zugriff auf die jeweiligen Speicherbereiche ausgelesen (der Treiber muss hierbei eventuelle Offsets, das Byte Ordering usw. selbst berücksichtigen), oder aber man verwendet die vom PCI-BIOS zur Verfügung gestellten Lese- und Schreib-Routinen für Memory und I/O- Bereiche eventuell weitere kartenspezifische Informationen auslesen und entsprechend darauf reagieren
" Karte schließlich über die Register in I/O- bzw. Memory-Bereich ansteuern
Der weitere Zugriff auf die angeschlossenen PCI-Geräte nach der Initialisierung und Konfiguration erfolgt also über Memory- und/oder I/O- Zugriffe auf die entsprechend zugeteilten Adreßbereiche. Für diesen Zugriff wäre zwar kein spezielles BIOS mehr erforderlich, aber das PCI-BIOS bietet aber eben auch hier eine einfache Zugriffsmöglichkeit auf diese Speicherbereiche/Register eines PCI-Gerätes, ohne sich um etwaige Offsets und Byte Orderings (Intel/Motorola) kümmern zu müssen.
Im folgenden wird also das Software-Interface beschrieben, das von den PCI-Gerätetreibern und anderer Systemsoftware genutzt werden kann, um Zugriff auf die PCI-Karten in ATARI-kompatiblen Systemen zu erhalten. Dieser Artikel soll zunächst das Konzept und die prinzipiellen Möglichkeiten aufzeigen, daher werden fürs Erste auch nicht sämtliche vom PCI-BIOS zur Verfügung gestellten Routinen beschrieben. Die restliche Funktionalität und weitere Einzelheiten können in der jeweils aktuellen Ausgabe der ATARI PCI BIOS Specification nachgelesen werden. Aller Vorraussicht nach wird man diese in naher Zukunft unter http://www.milan-computer.de finden.
Das PCI-BIOS installiert einen "_PCI"-Cookie, über den die BIOS-Routinen lokalisiert und angesprochen werden können. Für Programmierer gibt es hierfür bereits eine PCI-Library, die diese Aufgaben erledigt, und damit einen sehr einfachen Zugriff erlaubt. Die jeweils aktuellen Versionen des PCI-BIOS für den Hades und die entsprechende PCI-Library sind unter http://www.wvnet.at/privat/97021702/ zu finden. Weiters gibt es an dieser Stelle auch ein C-Beispielprogramm zur Verwendung dieser Library. Sobald die allgemein gültige ATARI PCI BIOS Specification im Internet erhältlich ist, wird es hier auch einen Link darauf geben. Beim Milan werden die BIOS-Routinen in einer der nächsten Versionen des Milan-TOS fix integriert sein, ein entsprechendes Programm im AUTO-Ordner ist im Gegensatz zum Hades also dann nicht mehr nötig. Ob und wann dies eventuell auch beim Hades der Fall sein wird, kann zu diesem Zeitpunkt noch nicht gesagt werden.
Jedem PCI-Gerät wird vom PCI-BIOS eine Geräte-Handle zugewiesen, mit dem diese Geräte dann angesprochen werden können. Rückschlüsse vom Geräte-Handle auf den betreffenden PCI-Slot sind nicht möglich, und auch nicht erwünscht. Im folgenden werden jeweils die Aufrufe in Assembler und C dargestellt, die Informationen zu den verwendeten Parametern ergänzen sich gegenseitig. Sämtliche PCI-BIOS Routinen müssen im Supervisor-Modus aufgerufen werden.
Die PCI-BIOS Fehlercodes
Die folgende Fehlercodes können im Fehlerfall von den BIOS-Routinen zurückgeliefert werden.
Tabelle 1: Fehlercodes der BIOS-Routinen
$00000000 | PCI_SUCCESSFUL |
$FFFFFFFE | PCI_FUNC_NOT_SUPPORTED |
$FFFFFFFD | PCI_BAD_VENDOR_ID |
$FFFFFFFC | PCI_DEVICE_NOT_FOUND |
$FFFFFFFB | PCI_BAD_REGISTER_NUMBER |
$FFFFFFFA | PCI_SET_FAILED |
$FFFFFFF9 | PCI_BUFFER_TOO_SMALL |
$FFFFFFF8 | PCI_GENERAL_ERROR |
$FFFFFFF7 | PCI_BAD_HANDLE |
$FFFFF001 | PCI_BIOS_NOT_INSTALLED |
$FFFFF000 | PCI_BIOS_WRONG_VERSION |
Find PCI device
Diese Funktion dient dazu, ein bestimmtes PCI-Gerät (eine PCI-Karte selbst kann ja wiederrum mehrere Funktionseinheiten = Geräte beherbergen) über Device und Vendor ID zu suchen. Damit findet ein Softwaretreiber also seine zugehörige Hardware, sofern sie auch im Rechner installiert wurde. Sollte eine Hardware mehrfach im System vorhanden sein, oder sich zwei Karten mit den gleichen IDs melden, so werden die einzelnen Karten über ihren Index abgefragt. Man beginnt dabei mit Index 0 und erhöht ihn dann solange, bis die Routine den Fehlercode PCI_DEVICE_NOT_FOUND zurückliefert.
Aufruf in Assembler:
Eingang:
D0.L Device ID in den Bits 31..16 (0 - $FFFF),
Vendor ID in den Bits 15..0 (0 - $FFFE)
D1.W Index (0 bis Anzahl der PCI-Geräte mit dieser ID)
Ausgang:
D0.L Geräte-Handle oder PCI-BIOS Fehlercode
Aufruf in C:
LONG handle = find_pci_device (ULONG id, UWORD index)
id: Device und Vendor ID des PCI-Gerätes
index: Index (bei mehreren gleichen Geräten)
Returnwert: positiv - Geräte-Handle
negativ - PCI-BIOS Fehlercode
Sonderfall:
mit Vendor ID FFFFhex kann man sämtliche im System vorhandene PCI-Geräte suchen, die Device ID wird in diesem Fall ignoriert.
Find PCI Classcode
Diese Funktion dient dazu, ein bestimmtes PCI-Gerät über ihren Classcode zu suchen. Mit dieser Funktion ist es z.B. möglich, die Grafikkarte zu erreichen, ohne den Hersteller oder die Device ID zu wissen. Diese Funktion führt natürlich nur dann zum Erfolg, wenn das PCI-Gerät auch einen der festgelegten Class Codes benutzt. Gibt es mehrere PCI-Geräte mit dem gleichen Classcode, so kann der Geräte-Treiber die weiteren Karten über ihren Index abfragen. Man beginnt dabei mit Index 0 an und erhöht ihn dann solange, bis die Routine den Fehlercode PCI_DEVICE_NOT_FOUND zurückliefert.
Aufruf in Assembler:
Eingang:
D0.L Classcode:
Bit 23..16 Base class (0 - $FF),
Bit 15..8 Sub class (0 - $FF),
Bit 7..0 Programming Interface (0)
Flags:
Bit 26 Base class 0:vergleichen 1:ignorieren
Bit 25 Sub class 0:vergleichen 1:ignorieren
Bit 24 Progr. interface: 0:vergleichen 1:ignorieren
Mit den Bits 24..26 kann man also angeben, welche Teile des Classcodes mit den gefundenen PCI-Geräten übereinstimmen müssen
D1.W Index (0 - Anzahl der PCI-Geräte mit diesem Classcode)
Ausgang:
D0.L Geräte-Handle oder PCI-BIOS Fehlercode
Aufruf in C:
LONG handle = find_pci_classcode (ULONG class, UWORD index)
class: Classcode des PCI-Gerätes
index: Index (bei mehreren gleichen Geräten)
Returnwert: positiv - Geräte-Handle
negativ - PCI-BIOS Fehlercode
Read Configuration {Byte|Word|Longword}
Diese drei Funktionen erlauben Lesezugriffe auf die Konfigurationsregister eines PCI-Gerätes, dessen Geräte-Handle zuvor mittels find_pci_device oder find_pci_classcode ermittelt wurde.
Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
A0.L Zeiger auf Ergebnisvariable
D1.B Adresse des Konfigurationsregisters (0,1,2,... für Byte-Zugriffe)
D1.B Adresse des Konfigurationsregisters (0,2,4,... für Wort-Zugriffe)
D1.B Adresse des Konfigurationsregisters (0,4,8,... für Langwort-Zugriffe)
Ausgang:
D0.L PCI-BIOS Fehlercode
gelesene Daten stehen nach dem Aufruf in der Ergebnisvariable
Aufruf in C:
LONG errorcode = read_config_byte (LONG handle, UBYTE reg, UBYTE *adresse)
LONG errorcode = read_config_word (LONG handle, UBYTE reg, UWORD *adresse)
LONG errorcode = read_config_longword (LONG handle, UBYTE reg, ULONG *adresse)
handle: Geräte-Handle des gewählten PCI-Gerätes
reg: Adresse des Konfigurationsregisters
adresse: Zeiger auf Ergebnisvariable
Returnwert: PCI-BIOS Fehlercode
Read Configuration {Byte|Word|Longword} FAST
Diese drei Funktionen erlauben das Lesen von Registern im Konfigurationsbereich ohne aufwendige Fehler- und Plausibilitätschecks und sind daher auch etwas schneller als ihre 3 Schwestern (daher besonders geeignet für Interrupt-Routinen und wenn man ganz genau weiß, was man tut ;-). Der Registerinhalt wird im Returnwert abgelegt. Dadurch können diese Funktionen in C-Programmen noch einfacher angewendet werden.
Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.B Adresse des Konfigurationsregisters (0,1,2,... für Byte-Zugriffe)
D1.B Adresse des Konfigurationsregisters (0,2,4,... für Wort-Zugriffe)
D1.B Adresse des Konfigurationsregisters (0,4,8,... für Langwort-Zugriffe)
Ausgang:
D0 gelesener Wert (8, 16 oder 32 Bits)
SR carry flag wird bei einem Fehler gesetzt (abhängig von der Implementation)
Aufruf in C:
UBYTE value = fast_read_config_byte (LONG handle, UBYTE reg)
UWORD value = fast_read_config_word (LONG handle, UBYTE reg)
ULONG value = fast_read_config_longword (LONG handle, UBYTE reg)
handle: Geräte-Handle des gewählten PCI-Gerätes
reg: Adresse des Konfigurationsregisters
Returnwert: Inhalt des Konfigurationsregisters
Beispiele:
liefert in den Bits 0-15 die Vendor ID und in den Bits 16-31 die Device ID
liefert die Vendor ID zurück
liefert die Device ID zurück
liefert schließlichen den Wert des Latency Timers
Write Configuration {Byte|Word|Longword}
Diese drei Routinen dienen zum Schreiben von Konfigurationsregistern eines PCI-Gerätes.
Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
D1.B Adresse des Konfigurationsregisters (0,1,2,... für Byte-Zugriffe)
D1.B Adresse des Konfigurationsregisters (0,2,4,... für Wort-Zugriffe)
D1.B Adresse des Konfigurationsregisters (0,4,8,... für Langwort-Zugriffe)
D2 zu schreibender Registerwert (8/16/32 bits)
Ausgang:
D0.L PCI-BIOS Fehlercode
Aufruf in C:
LONG errorcode = write_config_byte (LONG handle, UBYTE reg, UBYTE val)
LONG errorcode = write_config_word (LONG handle, UBYTE reg, UWORD val)
LONG errorcode = write_config_longword (LONG handle, UBYTE reg, ULONG val)
handle: Geräte-Handle des gewünschten PCI-Gerätes
reg: Adresse des Konfigurationsregisters
val: zu schreibender Registerwert
Returnwert: PCI-BIOS Fehlercode
Was passiert nun bei einem Interrupt ?
Für jeden PCI-Interrupt kann es eine ganze Kette von Interrupt-Handlern geben, da sich ja auch mehrere PCI-Geräte den selben Interrupt teilen können (siehe auch Artikel in der Ausgabe 4/98). über das Geräte-Handle kann nun ein Geräte-Treiber seinen eigenen Interrupt-Handler per hook_interrupt in die passende Kette eintragen lassen, ohne den dafür zuständigen Interrupt wissen zu müssen.
Tritt danach ein Interrupt auf, werden vom BIOS die in die Kette eingetragenen Handler der Reihe nach aufgerufen. Jeder Interrupt-Handler wird dabei mit jenem Parameter aufgerufen, der beim Aufruf von hook_interrupt spezifiziert wurde. Dieser Parameter ist vom Geräte-Treiber völlig frei wählbar, bei geeigneter Wahl könnte man mit einem einzigen Treiber auch mehrere (baugleiche) Geräte parallel bedienen. Die Interrupt-Handler müssen unmittelbar nach ihrem Aufruf prüfen, ob der Interrupt von der von ihnen betreuten Karte ausgelöst wurde, und dementsprechend reagieren. Das heißt, die Interruptursache ist zu beheben, und das entsprechende Bit des BIOS-internen Parameters ist zu setzen. Hat das entsprechende Gerät den Interrupt nicht ausgelöst, darf der Interrupt-Handler den BIOS-internen Parameter unter keinen Umständen verändern!
Der Handler selbst ist als gewöhnliche Prozedur zu betrachten, d.h. auch in Assembler wird der Interrupt-Handler mit RTS abgeschlossen.
Aufruf in Assembler:
Eingang:
A0.L Parameter, der mittels hook_interrupt spezifiziert wurde
D0.L BIOS-interner Parameter
Ausgang:
D0.L Bit 0 wird gesetzt, wenn der Interrupt von der betreuten PCI-Karte ausgelöst wurde,
anderenfalls darf dieser Parameter nicht geändert werden
Aufruf in C:
LONG value = interrupt_handler (LONG *value, LONG internal)
value: Parameter, der mittels hook_interrupt spezifiziert wurde
internal: BIOS-interner Parameter
Returnwert: BIOS-interner Parameter
Hook Interrupt Vector
Hängt den Interrupt-Handler des Treibers in die Kette des entsprechenden Interrupt-Kanals. War vorher noch kein Handler in der Kette, wird auch der betreffende PCI-Interrupt im Hostsystem aktiviert. Nach Auftreten des jeweiligen Interrupts wird somit der angegebene Interrupt-Handler aufgerufen. Allerdings ist insbesondere darauf zu achten, daß die Interrupts auf dem PCI-Gerät erst nach dem Aufruf dieser Funktion aktiviert werden dürfen, da es sonst zu spurious interrupts kommen kann.
Ist für das gewählte PCI-Gerät bereits ein Interrupt-Handler angemeldet, so erhält man einen entsprechenden PCI-BIOS Fehlercode. Es ist dringend davon abzuraten, per unhook_interrupt fremde Handler zu entfernen. Für diesen Zweck gibt es die Funktion get_card_used, über die eine Callback-Routine des fremden Treibers ermittelt werden kann. Der Aufruf dieser Callback-Routine gibt dem fremden Treiber dann die Möglichkeit, sich sauber und rückstandsfrei zu deinstallieren.
Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
A0.L Zeiger auf den Interrupt-Handler des Treibers
A1.L Zeiger auf Parameter für Interrupt-Handler
Ausgang:
D0.L PCI-BIOS Fehlercode
Aufruf in C:
LONG errorcode = hook_interrupt (LONG handle, ULONG *routine, ULONG *parameter)
handle: Geräte-Handle des gewünschten PCI-Gerätes
routine: Zeiger auf den Interrupt-Handler des Treibers
parameter: Zeiger auf Parameter für Interrupt-Handler; dies ist ein vom Treiber frei zu wählender Wert, der bei jedem Aufruf des Handlers vom BIOS mitübergeben wird. Dadurch könnte ein einziger Interrupt-Handler auch mehrere baugleiche PCI-Geräte bedienen.
Returnwert: PCI-BIOS Fehlercode
Unhook Interrupt Vector
Mit dieser Routine kann man einen mittels hook_interrupt angemeldeten Interrupt-Handler wieder entfernen. Der Treiber muss allerdings beachten, daß die Interrupts auf dem PCI-Gerät schon vor dem Aufruf dieser BIOS-Funktion deaktiviert werden müssen, da es sonst zu spurious interrupts kommen kann.
Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
Ausgang:
D0.L PCI-BIOS Fehlercode
Aufruf in C:
LONG errorcode = unhook_interrupt (LONG handle)
handle: Geräte-Handle des gewünschten PCI-Gerätes
Returnwert: PCI-BIOS Fehlercode
Get Resource Data
Liefert sämtliche Infos zu den Resourcen einer PCI-Karte (bzw. eines PCI-Gerätes im Fall von Multifunktionskarten). Die zurückgelieferten Infos dürfen von den Geräte-Treibern keinesfalls verändert werden. Der Geräte-Treiber kann an Hand der angebotenen Informationen (Byte ordering usw.) die Karte dann direkt ansprechen. Eine weitere Möglichkeit ist die Verwendung der BIOS-Routinen read_mem..._, write_mem..._, read_io..._ und write_io..._, wobei man sich dann um keinerlei Nebenbedingungen selbst kümmern muss.
Die Routine liefert einen Zeiger auf den ersten Resource Deskriptor des gewünschten PCI-Gerätes. Der Geräte-Treiber kann dann die weiteren Deskriptoren über einen Offset (Länge eines Deskriptors) erreichen. Der letzte Deskriptor des Geräts ist wiederrum speziell markiert. Die Reihenfolge der Despriptoren entspricht derer der Basisadreßregister im PCI-Konfigurationsbereich. Ein PCI-Gerät kann auch mehrere Resourcen des gleichen Typs anfordern/verwenden.
Aufruf in Assembler:
Eingang:
D0.L Geräte-Handle des gewünschten PCI-Gerätes
Ausgang:
D0.L Zeiger auf ersten Resource Deskriptor bzw. PCI-BIOS Fehlercode
Aufruf in C:
LONG pointer = get_resource (LONG handle)
handle: Geräte-Handle des gewünschten PCI-Gerätes
Returnwert: positiv - Zeiger auf Resourcen-Informationen (erster Deskriptor)
negativ - PCI-BIOS Fehlercode ****
Tabelle 3: Aufbau eines Resource Deskriptors
next | WORD | Länge dieser Struktur in Bytes, dient zur Ermittlung des nächsten Deskiptors |
flags | WORD | Resourcen-Typ und verschiedene andere Flags |
start | LONG | Startadresse der Resource im PCI Adreßbereich, ist die Resource nicht direkt ansprechbar, so ist die Adresse 0 |
length | LONG | Länge der Resource |
offset | LONG | Offset von der physikalischen CPU-Adresse zur PCI-Adresse |
dmaoffset | LONG | Offset für DMA-Transfers vom/zum Hauptspeicher |
private | x BYTE | BIOS-interne Daten, dürfen nicht verändert werden |
<BR>Um den Deskriptor der nächsten Resource des PCI-Gerätes zu ermitteln,
muss man zur Startadresse des aktuellen Deskriptors das Feld <B>next</B>
addieren. Das Feld <B>start</B> gibt den Beginn der entsprechenden Resource
im PCI-Adreßbereich an. Falls diese Resource nicht direkt ansprechbar
sein sollte, so steht in diesem Feld die Adresse 0. über <B>length</B>
kann man schließlich die Länge dieser Resource bestimmen. Der
PCI-Adreßbereich ist im allgemeinen nicht mit der von der CPU aus
gesehenen Adresse gleichzusetzen. Der Adreß-Offset, der zur PCI-Adresse
zu addieren ist, um die physikalische Adresse für die CPU zu ermitteln,
ist im Feld <B>offset</B> abgelegt.Der Eintrag <B>dmaoffset</B> gibt schließlich
den Offset an, der zur PCI-Adresse addiert werden muss, wenn es sich
um DMA-Transfers handelt.
<B>Tabelle 4: Die Flags im Resource-Deskriptor</B>
RSC_IO | $4000 | kennzeichnet einen I/O-Bereich, bei gelöschten Bit handelt es sich um einen memory-Bereich |
RSC_LAST | $8000 | letzte Resource für dieses PCI-Gerät |
FLG_8BIT | $0100 | 8 Bit Zugriffe werden unterstützt |
FLG_16BIT | $0200 | 16 Bit Zugriffe werden unterstützt |
FLG_32BIT | $0400 | 32 Bit Zugriffe werden unterstützt |
FLG_ENDMASK | $000F | kennzeichnet das Byte Ordering:
0: Motorola (big endian) 1: Intel (little endian), address swapped 2: Intel (little endian), lane swapped 3..14: reserviert 15: unbekannt, Zugriff nur über BIOS möglich |
Liegt die Resource im Motorola-Format (0) vor, braucht man nichts weiter zu tun. Sämtliche Zugriffe verlaufen wie gewohnt. Handelt es sich allerdings um das Intel-Format, ist in Abhängigkeit von der hardwaremäßigen Implementierung des PCI-Bus im Hostsystem zwischen zwei verschiedenen Möglichkeiten zu unterscheiden:
a) Im Falle von Intel, address swapped (1), ist bei 32Bit-Zugriffen keine Modifikation der Zugriffsadresse nötig. Für 16Bit-Zugriffe muss man allerdings die Adresse mit dem Wert 2 XOR-verknüpfen, bei 8Bit-Zugriffen muss man die jeweilige Adresse mit dem Wert 3 XOR-verknüpfen, um auf den Speicher korrekt zuzugreifen. Das zu lesende/schreibende Datum selbst liegt aber immer im richtigen Format vor.
b) im Falle von Intel, lane swapped (2), ist an den Adressen keinerlei Modifikation notwendig. Bei 8Bit-Zugriffen ist auch keine Konvertierung des Datums erforderlich, handelt es sich allerdings um 16Bit-Zugriffe, müssen im Datum High-Byte und Low-Byte vertauscht werden (ror.w #8,d0). Für 32Bit-Zugriffe müssen die 4 Bytes von der Form 1234 in das Format 4321 gebracht werden (ror.w d0:swap d0:ror.w d0). BRWer sich mit all dem nicht näher auseinandersetzen will, der sollte lieber doch gleich die oben erwähnten BIOS-Routinen verwenden, die die gegebenenfalls notwendigen Modifikationen selbständig durchführen. Ist das Byte Ordering unbekannt (15), muss man auf jeden Fall das BIOS für sämtliche Zugriffe bemühen.
Voila, mit diesen Informationen und dem entsprechenden BIOS kann man eine PCI-Karte bereits ein wenig quälen. Beim nächsten Mal werden wir uns dann noch gemeinsam die Routinen für I/O- und Memory-Zugriffe vorknöpfen. Zudem müssen wir auch noch einen Blick auf die Callback-Routinen werfen, die ja von den Geräte-Treibern zur Verfügung gestellt werden müssen. Aber vorerst mal viel Spaß beim Programmieren und dem Kennenlernen der schönen, neuen PCI-Welt...