RTL8139

Aus Lowlevel
Wechseln zu: Navigation, Suche

Grundsätzliches

Voraussetzungen

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. Dabei sind auf der Kommandozeile -net-Parameter anzugeben, z.B.:

qemu -fda lost.img -net nic,model=rtl8139 -net user

Mit diesen Parametern emuliert qemu eine Netzwerkkarte mit RTL8139-Chipsatz, die sich in einem virtuellen Netzwerk befindet. Nach außen ins große weite Internet kommt man über den virtuellen qemu-Router an der IP-Adresse 10.0.2.2.

Funktionweise

Wie bereits erwähnt, benutzt der Chipsatz verschiedene Ressourcen:

  • 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.
  • 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

  1. Vom PCI-Treiber den IRQ und die I/O-Ports holen
  2. IRQ-Handler und ggf. I/O-Ports registrieren
  3. 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.
  4. 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.
  5. 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
  6. Puffer für den Empfang (evtl auch zum Senden, das kann aber auch später passieren) initialisieren. Wir brauchen 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.
  7. 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.

Konstanten aus dem týndur-Code: <c> // Portnummern der Register

  1. define REG_ID0 0x00
  2. define REG_ID4 0x04
  1. define REG_TRANSMIT_STATUS0 0x10
  2. define REG_TRANSMIT_ADDR0 0x20
  3. define REG_RECEIVE_BUFFER 0x30
  4. define REG_COMMAND 0x37
  5. define REG_CUR_READ_ADDR 0x38
  6. define REG_INTERRUPT_MASK 0x3C
  7. define REG_INTERRUPT_STATUS 0x3E
  8. define REG_TRANSMIT_CONFIGURATION 0x40
  9. define REG_RECEIVE_CONFIGURATION 0x44

// Werte für die Register // Kontrollregister

  1. define CR_RESET (1 << 4)
  2. define CR_RECEIVER_ENABLE (1 << 3)
  3. define CR_TRANSMITTER_ENABLE (1 << 2)
  4. define CR_BUFFER_IS_EMPTY (1 << 0)

// Transmitter-Konfiguration

  1. define TCR_IFG_STANDARD (3 << 24)
  2. define TCR_MXDMA_512 (5 << 8)
  3. define TCR_MXDMA_1024 (6 << 8)
  4. define TCR_MXDMA_2048 (7 << 8)

// Receiver-Konfiguration

  1. define RCR_MXDMA_512 (5 << 8)
  2. define RCR_MXDMA_1024 (6 << 8)
  3. define RCR_MXDMA_UNLIMITED (7 << 8)
  4. define RCR_ACCEPT_BROADCAST (1 << 3)
  5. define RCR_ACCEPT_MULTICAST (1 << 2)
  6. define RCR_ACCEPT_PHYS_MATCH (1 << 1)

// Interrupt-Statusregister

  1. define ISR_RECEIVE_BUFFER_OVERFLOW (1 << 4)
  2. define ISR_TRANSMIT_OK (1 << 2)
  3. define ISR_RECEIVE_OK (1 << 0)

</c>

Senden eines Pakets

Senden ist eigentlich eine ganz einfache Übung, nachdem alles konfiguriert ist. Wir brauchen spätestens hier einen Sendepuffer, von dem wir die physische Adresse wissen. Anschließend müssen wir einfach der Netzwerkkarte diese Adresse geben.

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 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 týndur benutzt wird: <c>

   // Adresse und Größe des Buffers setzen
   // In TASD (REG_TRANSMIT_STATUS) wird das OWN-Bit gelöscht, was die
   // tatsächliche Übertragung anstößt.
   write_register_dword(netcard,
       REG_TRANSMIT_ADDR0 + (4 * netcard->cur_buffer),
       (dword) netcard->buffer.phys);
   write_register_dword(netcard,
       REG_TRANSMIT_STATUS0 + (4 * netcard->cur_buffer),
       size);
   // Die vier Deskriptoren müssen der Reihe nach verwendet werden,
   // auch wenn wir momentan nur einen einzigen Puffer verwenden
   // und immer warten.
   netcard->cur_buffer++;
   netcard->cur_buffer %= 4;

</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) 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

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, 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.

Bytes 0-1:     Paketheader (Bit 0 = ROK)
Bytes 2-3:     Länge des Pakets
Bytes 4-n:     Daten (Payload)
Bytes n+1-n+4: CRC-Prüfsumme

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, 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 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>

   // Den aktuellen Offset im Lesepuffer anpassen. Jedes Paket ist
   // dword-aligned, daher anschließend Aufrundung.
   netcard->rx_buffer_offset += length + 4;
   netcard->rx_buffer_offset = (netcard->rx_buffer_offset + 3) & ~0x3;
   // Überläufe abfangen
   netcard->rx_buffer_offset %= 0x2000;
   write_register_word(netcard, REG_CUR_READ_ADDR,
       netcard->rx_buffer_offset - 0x10);

</c>

Sonstiges

MAC-Adresse

Die MAC-Adresse ist in den Registern IDR0 bis IDR5 (0x00 bis 0x05) gespeichert und kann dort sowohl gelesen als auch gesetzt werden.

Siehe auch

Weblinks