RTL8139 — Änderungen 2036 → 2040

Zeile 1: Zeile 1:
 
==Grundsätzliches==
 
==Grundsätzliches==
 
===Voraussetzungen===
 
===Voraussetzungen===
−
Ich gehe davon aus, daß jeder, der vorhat, mit diesem Tutorial einen Netzwerktreiber für den RTL8139-Chip zu schreiben, mit seinem System bereits auf einem gewissen Stand ist. Grundlagen wie das Empfangen von IRQs oder Speicherverwaltung sind selbstverständlich, außerdem sollte eine Möglichkeit bestehen, IRQs und Ports von PCI-Geräten herauszufinden.
+
Ich gehe davon aus, dass jeder, der vorhat, mit diesem Tutorial einen Netzwerktreiber für den RTL8139-Chip zu schreiben, mit seinem System bereits auf einem gewissen Stand ist. Grundlagen wie das Empfangen von IRQs oder Speicherverwaltung sind selbstverständlich, außerdem sollte eine Möglichkeit bestehen, IRQs und Ports von PCI-Geräten herauszufinden.
  
 
Als Emulator empfehle ich qemu (ich benutze Version 0.8.2, vermutlich ab 0.8.0 kann der RTL8139-Chipsatz emuliert werden). Dabei sind auf der Kommandozeile -net-Parameter anzugeben, z.B.:
 
Als Emulator empfehle ich qemu (ich benutze Version 0.8.2, vermutlich ab 0.8.0 kann der RTL8139-Chipsatz emuliert werden). Dabei sind auf der Kommandozeile -net-Parameter anzugeben, z.B.:
Zeile 12: Zeile 12:
 
* Ports: Über I/O-Ports lassen sich die Register der Netzwerkkarte ansprechen, über die die grundlegende Konfiguration vorgenommen wird.
 
* Ports: Über I/O-Ports lassen sich die Register der Netzwerkkarte ansprechen, über die die grundlegende Konfiguration vorgenommen wird.
 
* IRQ: Unter anderem wird ein IRQ ausgelöst, sobald ein Ethernet-Paket eingetroffen ist oder ein Paket verschickt wurde.
 
* IRQ: Unter anderem wird ein IRQ ausgelöst, sobald ein Ethernet-Paket eingetroffen ist oder ein Paket verschickt wurde.
−
* Speicher: Zum Senden und Empfangen wird jeweils ein Ringpuffer im Arbeitsspeicher benutzt. Ringpuffer bedeutet, daß der Puffer zunächst ganz normal von vorne bis zum Ende gefüllt wird. Anschließend wird wieder zum Anfang des Puffers umgebrochen und dort mit dem Füllen fortgefahren (falls die alten Daten dort abgearbeitet sind, ansonsten kommt es zu einem Fehler).
+
* Speicher: Zum Senden und Empfangen wird jeweils ein Ringpuffer im Arbeitsspeicher benutzt. Ringpuffer bedeutet, dass der Puffer zunächst ganz normal von vorne bis zum Ende gefüllt wird. Anschließend wird wieder zum Anfang des Puffers umgebrochen und dort mit dem Füllen fortgefahren (falls die alten Daten dort abgearbeitet sind, ansonsten kommt es zu einem Fehler).
  
 
==Initialisierung==
 
==Initialisierung==
 
# Vom PCI-Treiber den IRQ und die I/O-Ports holen
 
# Vom PCI-Treiber den IRQ und die I/O-Ports holen
 
# IRQ-Handler und ggf. I/O-Ports registrieren  
 
# IRQ-Handler und ggf. I/O-Ports registrieren  
−
# Einen Reset der Karte durchführen: Bit 4 im Befehlsregister (0x37, 1 Byte) setzen. Wenn ich hier Portnummern von Registern angebe, ist damit der Offset zum ersten Port der Karte gemeint, der durch die PCI-Funktionen ermittelt werden muß.
+
# Einen Reset der Karte durchführen: Bit 4 im Befehlsregister (0x37, 1 Byte) setzen. Wenn ich hier Portnummern von Registern angebe, ist damit der Offset zum ersten Port der Karte gemeint, der durch die PCI-Funktionen ermittelt werden muss.
 
# Aktivieren des Transmitters und des Receivers: Setze Bits 2 und 3 (TE bzw. RE) im Befehlsregister (0x37, 1 Byte). Dies darf angeblich nicht erst später geschehen, da die folgenden Befehle ansonsten ignoriert würden.
 
# Aktivieren des Transmitters und des Receivers: Setze Bits 2 und 3 (TE bzw. RE) im Befehlsregister (0x37, 1 Byte). Dies darf angeblich nicht erst später geschehen, da die folgenden Befehle ansonsten ignoriert würden.
−
# TCR (Transmit Control Register, 0x40, 4 Bytes) und RCR (Receive Control Register, 0x44, 4 Bytes) setzen. An dieser Stelle nicht weiter kommentierter Vorschlag: TCR = 0x03000700, RCR = 0x0000070a
+
# TCR (Transmit Configuration Register, 0x40, 4 Bytes) und RCR (Receive Configuration Register, 0x44, 4 Bytes) setzen. An dieser Stelle nicht weiter kommentierter Vorschlag: TCR = 0x03000700, RCR = 0x0000070a
−
# Puffer für den Empfang (evtl auch zum Senden, das kann aber auch später passieren) initialisieren. Wir brauchen für bei den vorgeschlagenen Werten 8K + 16 Bytes für den Empfangspuffer und einen ausreichend großen Sendepuffer. Was ausreichend bedeutet, ist dabei davon abhängig, welche Menge wir auf einmal absenden wollen. Anschließend muß die physische (!) Adresse des Empfangspuffers nach RBSTART (0x30, 4 Bytes) geschrieben werden.
+
# Puffer für den Empfang (evtl auch zum Senden, das kann aber auch später passieren) initialisieren. Wir brauchen für bei den vorgeschlagenen Werten 8K + 16 Bytes für den Empfangspuffer und einen ausreichend großen Sendepuffer. Was ausreichend bedeutet, ist dabei davon abhängig, welche Menge wir auf einmal absenden wollen. Anschließend muss die physische (!) Adresse des Empfangspuffers nach RBSTART (0x30, 4 Bytes) geschrieben werden.
 
# Interruptmaske setzen (0x3C, 2 Bytes). In diesem Register können die Ereignisse ausgewählt werden, die einen IRQ auslösen sollen. Wir nehmen der Einfachkeit halber alle und setzen 0xffff.
 
# Interruptmaske setzen (0x3C, 2 Bytes). In diesem Register können die Ereignisse ausgewählt werden, die einen IRQ auslösen sollen. Wir nehmen der Einfachkeit halber alle und setzen 0xffff.
  
Zeile 71: Zeile 71:
 
Eine Besonderheit gibt es noch zu erwähnen: Ringpuffer überall. Die Karte kennt vier Deskriptoren für das Senden, die immer brav der Reihe nach benutzt werden müssen, ansonsten passiert einfach gar nichts. Das kann man durchaus ausnutzen, indem man den nächsten Puffer schon vorbereitet, solange der erste nicht komplett verschickt ist, für den Anfang reicht es aber, eins nach dem anderen zu machen.
 
Eine Besonderheit gibt es noch zu erwähnen: Ringpuffer überall. Die Karte kennt vier Deskriptoren für das Senden, die immer brav der Reihe nach benutzt werden müssen, ansonsten passiert einfach gar nichts. Das kann man durchaus ausnutzen, indem man den nächsten Puffer schon vorbereitet, solange der erste nicht komplett verschickt ist, für den Anfang reicht es aber, eins nach dem anderen zu machen.
  
−
Zuerst muß die Adresse nach TSDx (vier Register ab 0x10, je 4 Bytes) geschrieben werden, anschließend muß in TSAD (vier Register ab 0x20, je 4 Bytes) die Größe der zu übertragenden Daten eingetragen werden (Bits 0-12). Gleichzeitig wird damit das Bit 13 (OWN) gelöscht, was die Übertragung anstößt.
+
Zuerst muss die Adresse nach TSDx (vier Register ab 0x10, je 4 Bytes) geschrieben werden, anschließend muss in TSAD (vier Register ab 0x20, je 4 Bytes) die Größe der zu übertragenden Daten eingetragen werden (Bits 0-12). Gleichzeitig wird damit das Bit 13 (OWN) gelöscht, was die Übertragung anstößt.
  
 
Im folgenden ein Codebeispiel, wie es in LOST benutzt wird:
 
Im folgenden ein Codebeispiel, wie es in LOST benutzt wird:
Zeile 93: Zeile 93:
 
</c>
 
</c>
  
−
Sobald die Karte fertig ist, schickt sie einen IRQ und in ISR (0x3E, 2 Bytes) ist das Bit 2 (TOK = Transmit OK) gesetzt. Nachdem die nötigen Aktionen durchgeführt wurden (z.B. Puffer wieder freigeben) muß das Bit gelöscht werden. Bits in ISR werden dadurch gelöscht, daß man sie beim Schreiben setzt. Um TOK zu löschen, würde man also 0x04 nach ISR schreiben.
+
Sobald die Karte fertig ist, schickt sie einen IRQ und in ISR (0x3E, 2 Bytes) ist das Bit 2 (TOK = Transmit OK) gesetzt. Nachdem die nötigen Aktionen durchgeführt wurden (z.B. Puffer wieder freigeben) muss das Bit gelöscht werden. Bits in ISR werden dadurch gelöscht, dass man sie beim Schreiben setzt. Um TOK zu löschen, würde man also 0x04 nach ISR schreiben.
  
 
==Empfangen==
 
==Empfangen==
−
Sobald ein Paket eintrifft, wird ebenfalls ein IRQ ausgelöst und in ISR (0x3E, 2 Bytes) ist das Bit 0 (ROK = Receive OK) gesetzt. Auch hier gilt, daß das Bit nach erfolgter Behandlung des Interupts durch Schreiben von 0x01 nach ISR gelöscht werden möchte.
+
Sobald ein Paket eintrifft, wird ebenfalls ein IRQ ausgelöst und in ISR (0x3E, 2 Bytes) ist das Bit 0 (ROK = Receive OK) gesetzt. Auch hier gilt, dass das Bit nach erfolgter Behandlung des Interupts durch Schreiben von 0x01 nach ISR gelöscht werden möchte.
  
−
Bei der Behandlung des Interrupts ist zu beachten, daß mehrere Pakete eingetroffen sein könnten. Daher müssen in einer Schleife solange Pakete eingelesen werden, bis Bit 0 (BUFE = Buffer Empty) im Befehlsregister (CR, 0x37, 1 Byte) gesetzt ist.  
+
Bei der Behandlung des Interrupts ist zu beachten, dass mehrere Pakete eingetroffen sein könnten. Daher müssen in einer Schleife solange Pakete eingelesen werden, bis Bit 0 (BUFE = Buffer Empty) im Befehlsregister (CR, 0x37, 1 Byte) gesetzt ist.  
  
 
<pre>
 
<pre>
Zeile 109: Zeile 109:
 
Was wir unbedingt wissen müssen, ist unsere aktuelle Position im Ringpuffer (am Anfang 0). An dieser Stelle können wir zwei Bytes Paketheader abholen (Bit 0 = ROK sollte dort gesetzt sein) und darauf folgend zwei Bytes Länge abholen. Entsprechend viele Bytes folgen anschließend noch, die das eigentliche Paket und am Ende vier Bytes CRC-Prüfsumme enthalten (letztere kann man einfach ignorieren).  
 
Was wir unbedingt wissen müssen, ist unsere aktuelle Position im Ringpuffer (am Anfang 0). An dieser Stelle können wir zwei Bytes Paketheader abholen (Bit 0 = ROK sollte dort gesetzt sein) und darauf folgend zwei Bytes Länge abholen. Entsprechend viele Bytes folgen anschließend noch, die das eigentliche Paket und am Ende vier Bytes CRC-Prüfsumme enthalten (letztere kann man einfach ignorieren).  
  
−
Beim Auslesen der Daten ist unbedingt zu beachten, daß es sich um einen Ringpuffer handelt. Das bedeutet, daß sich irgendwo in der Mitte der Daten der Umbruch befinden könnte, wo es wieder bei Null losgeht. Durch anfängliches Ignorieren dieser Tatsache habe ich meinen Treiber in LOST schon abstürzen lassen.
+
Beim Auslesen der Daten ist unbedingt zu beachten, dass es sich um einen Ringpuffer handelt. Das bedeutet, dass sich irgendwo in der Mitte der Daten der Umbruch befinden könnte, wo es wieder bei Null losgeht. Durch anfängliches Ignorieren dieser Tatsache habe ich meinen Treiber in LOST schon abstürzen lassen.
  
−
Anschließend muß der Netzwerkkarte mitgeteilt werden, daß wir etwas gelesen haben. Dazu schreiben wir in das Register CAPR (Current Address of Packet Read, 0x38, 2 Bytes) das Offset des nächsten erwarteten Pakets:
+
Anschließend muss der Netzwerkkarte mitgeteilt werden, dass wir etwas gelesen haben. Dazu schreiben wir in das Register CAPR (Current Address of Packet Read, 0x38, 2 Bytes) das Offset des nächsten erwarteten Pakets:
  
 
<c>
 
<c>