Sonder-Edition PCI-BIOS, Part3
Stand: 09. 07. 1998
Das PCI-BIOS 3
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: 1) device_vendor = fast_read_config_longword (handle, 0) liefert in den Bits 0-15 die Vendor ID und in den Bits 16-31 die Device ID 2) vendor = fast_read_config_word (handle, 0) liefert die Vendor ID zurück 3) device = fast_read_config_word (handle, 2) liefert die Device ID zurück 4) timer = fast_read_config_byte (handle, 13) 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 muß 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 muß. 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
Um den Deskriptor der nächsten Resource des PCI-Gerätes zu ermitteln, muß man zur Startadresse des aktuellen Deskriptors das Feld next addieren. Das Feld start 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 length 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 offset abgelegt.Der Eintrag dmaoffset gibt schließlich den Offset an, der zur PCI-Adresse addiert werden muß, wenn es sich um DMA-Transfers handelt.
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 muß man allerdings die Adresse mit dem Wert 2 XOR-verknüpfen, bei 8Bit-Zugriffen muß 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). Wer 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), muß man auf jeden Fall das BIOS für sämtliche Zugriffe bemühen. Und wie geht's weiter? 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... Milan-Homepage:
http://www.milan-computer.de Letzte Änderung am 08. JuLi 1998, 13:07 © 1998 thomas raukamp communications, alle Rechte vorbehalten "Und umzuschaffen das Geschaffene, daß sich's nicht zum Starren waffne, wirkt ewiges lebendiges Tun." (Goethe) |