64net/2 und RR-Net

Allgemeines

Für das von Jens Schönfeld vertriebene Retro Replay gibt es für den darauf befindlichen Clockport einen Netzwerkadapter, ebenfalls von Individual Computers. Dieser enthält einen CS8900A Chip für 10MBit Ethernet-Netzwerke. In der Welt der PCs gibt es mittlerweile schnelleres, für den C64 ist es bereits unmöglich die volle Bandbreite von 10MBit auszunutzen. Es stehen für einen schnellen Datentransfer sowie für Filmstreaming (Porno) Tür und Tor offen. Es liegt also nahe, das bereits funktionierende 64net/2 auch für diese Zwecke zu nutzen, dazu ist ein weiteres Kommunikationsmodul ausreichend, dass anstatt den anderen Transfermodi, wie etwa über den Parallelport, nun die Netzwerkschnittstelle nutzt. Idealerweise kann man am PC hierzu direkt einen Socket programmieren, über den man bereits fertige Pakete verschicken und empfängen kann. Damit es auf C64-Seite nicht zuviel Overhead gibt, ist das UDP-Protokoll ein guter Mittelweg. Der Server (also PC) wartet hierbei auf Port 5000 auf neue Nachrichten. Die grossen Pluspunkte liegen bei hohen Übertragunsgraten (optimiert bis zu 44kb/s) und der Möglichkeit mehrere C64 über herkömmliches Netzwerk an einen PC anzuschliessen. Ja, 64net/2 ist hiermit Multiclient-fähig.

Erwartete Probleme

Vergabe einer MAC-Adresse
Leider besitzt das RR-Net Modul keine eigene MAC-Adresse, diese muss von Hand vergeben werden. Jedoch besteht die Gefahr, dass dann die Adresse nicht mehr eindeutig ist.


ARP Auflösung wird vom C64 nicht unterstützt
Eine zusätzliche ARP-Auflösung sowie ARP-Cache hätten keinen Platz im Kernal, zudem fehlt es an Speicher um einen Cache zu halten. Es werden daher MAC-Adresse und IP statisch angegeben. Auf der PC Seite geschieht das z.B. mittels 'arp -s 00:00:00:64:64:64 192.168.2.64'. Mittlerweile werden diese Einträge aus einem Konfigurationsfile gelesen und automatisch über ioctls vorgenommen.


Einhalten der Paketreihenfolge
Damit die Pakete auch in der Reihefolge am PC Eintreffen, in der sie vom C64 versendet werden (und umgekehrt), gibt es mehrere Möglichkeiten:

- Synchronisation durch Acknowledgepakete:
Nachteil: Was tun bei Paketverlust?

- Paketnummern:
Nachteil:Der C64 muss sich Status merken. Hierzu benötigt man aber Speicher oder Adressen aus der Zeropage. Retransmits sind zudem nur möglich wenn Pakete gepuffert werden - nur wo?


Fehlerbehandlung
Leicht auszuschliessen sind Pakete des falschen Typs, und von falschen Hosts. Paketverlust hingegen ist nur schwer zu behandeln (siehe oben)


Fehlender Speicher am C64 für Buffer und Status
Um möglichst kompatibel zu sein und den Hauptspeicher in keinster Weise mit Statusdaten oder Pufferinhalten zu belegen muss eine Verarbeitung ohne Puffer stattfinden. Dies ist möglich, da auch auf dem Chip Puffer vorhanden ist (allerdings gelatched, es ist also kein Random Access möglich). Ein völlig Statusloser Code ist leider nicht möglich, da zumindest ein Counter für bereits gelesene und gesendete Bytes nötig ist. Die seriellen Routinen nutzen lediglich $A5 als Counter. Diese Adresse können wir für also unsere Zwecke nutzen, reicht aber nicht aus für zwei Zähler. Glücklicherweise sind einige Register des CS8900A unbenutzt aber dennoch funktionabel (wie etwa die Schnittstelle zur EEPROM Ansteuerung die auf dem RR-Net ungenutzt bleibt). Diese Register kann man ideal als Zwischenspeicher für die zwei benötigten Zähler und ggf. weitere Stati nutzen.


Teilweise gelesene/gesendete Pakete
Da ohne Puffer gearbeiten wird, sind teilweise gelesene Pakete unwiederruflich verloren, sobald ein Paket versendet ist oder komplett ausgelesen wurde (read/write once). Auf der anderen Seite wollen wir aber die Paketgrösse maximieren, um einen optimalen Durchsatz zu erreichen. Das heisst eine Umgehung des Problems durch eine Payloadgrösse von einem Byte ist nicht immer eine Lösung. Für ausgehende Pakete kann der Rest des durch den Header eigentlich grösser angekündigten Pakets aufgefüllt werden (padding) und die tatsächliche Payloadlänge am Ende des Paketes vermerkt werden. Somit ist gewährleistet dass auch teilweise gefüllte Datenpakete verschickt werden können, wenn dies nötig ist (z.B. in der Regel das letztes Datenpaket beim Speichern). Der umgekehrte Fall ist eigentlich nicht zwingend notwendig. Einzige Ausnahme stellt das get#1 und input#1 Basic-Kommando dar. Diese Kommandos senden pro Byte ein TALK und ein UNTALK Kommando. Bei jedem Kommando wird also das empfangene restliche Paket verworfen (da ein Kommandopaket gesendet werden muss), wovon die Gegenstelle jedoch nichts mitbekommt. Möglich wäre es, die Anzahl der bereits gelesenen Bytes im gesendeten Kommando anzuhängen, so dass der PC entsprechend darauf reagieren kann und die Daten nochmals sendet sobald er dazu aufgefordert wird. Das Kommandopaket würde dann sozusagen als indirekter Teilacknowledge agieren (Kommand + x Bytes wurden schon gelesen, müssen also nicht mehr neu gesendet werden).


16 Bit Architektur
Die 16 Register des CS8900A sind jeweils über zwei 8 Bit breite Register aufgeteilt (Lowbyte/Highbyte). Byteweises Auslesen/Schreiben verursacht also mehr Komplexität und Code.


Pakettypen

Acknowledge:
0x41, 1 Füllbyte

Error:
0x45, 1 Byte Fehler

Command:
0x43, 1 Byte Kommando, evtl. 1 Byte incounter

Data (von PC):
0x44, 1 Byte Länge, Data (max. 254 Bytes)

Data (von C64):
0x44, 1 Füllbyte, maximal 192 Bytes Data (padded), 1 Byte Länge


Der Transfer im Detail

Der C64 initiert i.d.R. mit einem LISTEN Kommando einen Transfer auf dem seriellen Bus. Das bedeutet das in unserem Fall also zuerst ein Kommandopaket an den PC geht. Wurde zuvor bereits damit angefangen Daten zu senden (OUTLEN>0) werden diese zuerst aufgefüllt (Padding) und der aktuelle Counter angehängt und versendet (Flush). Nun ist der Puffer des Chips frei und es kann das eigentliche Kommandopaket versendet werden. Auf jedes ausgehende Paket muss das Acknowledgepaket der Gegenseite abgewartet werden, um den Transfer synchron zu halten.

Typischerweise sieht eine Übertragung also etwa wie folgt aus (LOAD"TEST",9):

Zuerst Flushen, falls nötig
PC <---- C64     Data
PC ----> C64     Acknowledge

Dann erstes Kommando senden
PC <---- C64     Command 0x29     (LISTEN)
PC ----> C64     Acknowledge
PC <---- C64     Command 0xF0     (Secondary Address, OPEN)
PC ----> C64     Acknowledge

Filename senden, theoretisch beliebig viele Datenpakete
PC <---- C64     Data 0x54, 0x45, 0x53, 0x54 ... 0x04
PC ----> C64     Acknowledge

PC <---- C64     Command 0x3F     (UNLISTEN)
PC ----> C64     Acknowledge

Zum Datenversand auffordern
PC <---- C64     Command 0x49     (TALK)
PC ----> C64     Acknowledge
PC <---- C64     Command 0x60     (Secondary Address, LOAD)
PC ----> C64     Acknowledge

Daten übertragen, n Pakete
PC ----> C64     Data 0xFE ...
PC <---- C64     Acknowledge
...

Ende des Datenstroms signalisieren
PC ----> C64     Error 0x40       (EOF -> $90)
PC <---- C64     Acknowledge

File schliessen
PC <---- C64     Command 0x29     (LISTEN)
PC ----> C64     Acknowledge
PC <---- C64     Command 0xE0     (Secondary Address, CLOSE)
PC ----> C64     Acknowledge

PC <---- C64     Command 0x3F     (UNLISTEN)
PC ----> C64     Acknowledge

Kernalpatch

Die eigentlichen Veränderungen Betreffen nur den byteweisen Transfer wie er auch auf dem seriellen Bus geschieht ($EE13, $EDDD). Für den Netzwerktransfer sehen diese etwa wie folgt aus:

getbyte
        lda inlen       ;bytecounter laden
        bne ok1         ;schon daten da?
        jsr receive     ;nein, erstes paket holen
        beq ok1         ;alles ok?
        sta $90         ;nein, fehler setzen
        rts
ok1
        lda $a5         ;low oder highbyte?
        tay             ;index bilden
        eor #$01        ;counter invertieren
        sta $a5         ;speichern
        lda rxtx,y      ;low/hibyte holen (16bit register!)
        sta $a4         ;byte speichern
        dec inlen       ;byte done
        bne ok2         ;letztes byte?
        jsr receive     ;ja, weiteres paket holen
        beq ok2         ;alles ok?
        sta $90         ;nein, fehler setzen
ok2
        rts

sendbyte
        lda outlen      ;bytecounter laden
        bne ok1         ;noch platz?
        lda #$c0        ;nein
        sta outlen      ;neues paket
        jsr sendheader  ;header senden
ok1
        lda outlen      ;low oder hibyte?
        tay             ;index bilden
        lda $95         ;byte laden
        sta rxtx,y      ;low/hibyte senden
        dec outlen      ;byte done
        rts

Ein Kommando ist ein spezielles Sendbyte. Hier wird einfach ein anderer Header versandt und anschliessend geflusht. Für Load uns Save können natürlich optimierte Routinen zum Einsatz kommen, was aber nicht zwingend notwendig ist. Dennoch ist mit optimierten Routinen die Geschwindigkeit um ein Vielfaches höher (~Faktor 6). Es können momentan optimierte Ladegeschwindigkeiten von bis zu 39kb/s erreicht werden. Hierbei bieten eine vorberechnete Checksumme (nur noch Längenfeld aufaddieren) weitere Optimierungsmöglichkeiten.