==Grundsätzliches==
===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.
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.:
qemu -fda lost.img -net nic,model=rtl8139 -net user
===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, 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).
==Initialisierung==
# Vom PCI-Treiber den IRQ und die I/O-Ports holen
# 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ß.
# 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
# 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.
# 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 LOST-Code:
// Portnummern der Register
#define REG_ID0 0x00
#define REG_ID4 0x04
#define REG_TRANSMIT_STATUS0 0x10
#define REG_TRANSMIT_ADDR0 0x20
#define REG_RECEIVE_BUFFER 0x30
#define REG_COMMAND 0x37
#define REG_CUR_READ_ADDR 0x38
#define REG_INTERRUPT_MASK 0x3C
#define REG_INTERRUPT_STATUS 0x3E
#define REG_TRANSMIT_CONFIGURATION 0x40
#define REG_RECEIVE_CONFIGURATION 0x44
// Werte für die Register
// Kontrollregister
#define CR_RESET (1 << 4)
#define CR_RECEIVER_ENABLE (1 << 3)
#define CR_TRANSMITTER_ENABLE (1 << 2)
#define CR_BUFFER_IS_EMPTY (1 << 0)
// Transmitter-Konfiguration
#define TCR_IFG_STANDARD (3 << 24)
#define TCR_MXDMA_512 (5 << 8)
#define TCR_MXDMA_1024 (6 << 8)
#define TCR_MXDMA_2048 (7 << 8)
// Receiver-Konfiguration
#define RCR_MXDMA_512 (5 << 8)
#define RCR_MXDMA_1024 (6 << 8)
#define RCR_MXDMA_UNLIMITED (7 << 8)
#define RCR_ACCEPT_BROADCAST (1 << 3)
#define RCR_ACCEPT_MULTICAST (1 << 2)
#define RCR_ACCEPT_PHYS_MATCH (1 << 1)
// Interrupt-Statusregister
#define ISR_RECEIVE_BUFFER_OVERFLOW (1 << 4)
#define ISR_TRANSMIT_OK (1 << 2)
#define ISR_RECEIVE_OK (1 << 0)
==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 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.
Im folgenden ein Codebeispiel, wie es in LOST benutzt wird:
// 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;
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.
==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.
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.
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, 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.
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:
// 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);
==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.
==Web-Links==
* [http://www.cs.usfca.edu/~cruse/cs326/RTL8139D_DataSheet.pdf RTL8139-Datenblatt] (Kapitel 5: Beschreibung aller Register)
* [http://www.cs.usfca.edu/~cruse/cs326/RTL8139_ProgrammersGuide.pdf RTL8139(A/B) Programming Guide (V0.1)]
[[Category:Tutorials]]
[[Category:Hardware]]