;;; ITS Ethernet support -*-MIDAS-*- ;;; ;;; Copyright (C) 2018 Adam Sampson ;;; ;;; This program is free software; you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation, either version 2 of the License, or ;;; (at your option) any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program. If not, see . ;FIXME The big one - interrupts? Compare with CHAOS... ;;; Generic Ethernet support %NE32==37777777777 %NE16==177777 %NE8==377 .BEGIN %NEH ;Fields in the Ethernet header PAD==2 ;padding at start of buffer LENGTH== WORDS== ;FIXME Would all the MAC stuff be easier if this was 3 16-bit fields? DMAC0==<.BP <%NE16_4>,0> DMAC12==<.BP <%NE32_4>,1> SMAC01==<.BP <%NE32_4>,2> SMAC2==<.BP <%NE16_20.>,3> TYPE==<.BP <%NE16_4>,3> ;see %NETYP .END %NEH .BEGIN %NETYP ;Ethernet frame types IP==4000 ARP==4006 .END %NETYP .BEGIN %NEAH ;Fields in an Ethernet+IPv4 ARP packet LENGTH==<2+2+1+1+2+6+4+6+4> ;should be this long WORDS==</4> HRD==<.BP <%NE16_20.>,0> ;see %NEAHR PRO==<.BP <%NE16_4>,0> ;see %NETYP HLN==<.BP <%NE8_28.>,1> ;must be 6 for Ethernet MAC PLN==<.BP <%NE8_20.>,1> ;must be 4 for IPv4 address OP==<.BP <%NE16_4>,1> ;see %NEAOP SHA01==<.BP <%NE32_4>,2> SHA2==<.BP <%NE16_20.>,3> SPA0==<.BP <%NE16_4>,3> ;SPA split across words SPA1==<.BP <%NE16_20.>,4> THA0==<.BP <%NE16_4>,4> THA12==<.BP <%NE32_4>,5> TPA==<.BP <%NE32_4>,6> .END %NEAH .BEGIN %NEAHR ;ARP hardware type ETHER==1 .END %NEAHR .BEGIN %NEAOP ;ARP operation REQ==1 REPLY==2 .END %NEAOP %NEATS==64. ;Max number of ARP table entries .BEGIN %NEAT ;ARP table entry WORDS==3 HA01==<.BP <%NE32>,0> ;MAC address HA2==<.BP <%NE16>,1> PA==<.BP <%NE32>,2> ;IPv4 address .END %NEAT EBLK ETHME: BLOCK %NEAT"WORDS ;Our MAC address, as ARP table entry ;(device driver must initialise this) ETHHBF: BLOCK %NEH"WORDS ;Ethernet header unpacked ETHABF: BLOCK %NEAH"WORDS ;ARP packet unpacked ETHAT: BLOCK <%NEATS*%NEAT"WORDS> ;ARP table ETHATS: 0 ;Number of entries in ARP table BBLK ;Process a received packet. ;R is buffer. Length in bytes is I. May clobber A-I, R. ; ETHRX: MOVEI TT,ETHHBF ;Unpack Ethernet header into ETHHBF HRL TT,R BLTUB TT, BUG INFO,[RX packet length ],OCT,I,[ ptr ],OCT,R SUBI I,%NEH"LENGTH ;I now contains data length ADDI R,%NEH"WORDS ;R points to data LDB A,[%NEH"TYPE+ETHHBF] ;BUG INFO,[etype=],OCT,A CAIN A,%NETYP"IP JRST ETHRXI CAIN A,%NETYP"ARP JRST ETHRXA BUG INFO,[Unknown Ethernet type],OCT,A POPJ P, ;Process an ARP packet. ; ETHRXA: ;BUG INFO,[ARP length ],OCT,I CAIGE I,%NEAH"LENGTH ;Check size first JRST ETHRAS MOVEI TT,ETHABF ;Unpack packet into ETHABF HRL TT,R BLTUB TT, LDB A,[%NEAH"HRD+ETHABF] LDB B,[%NEAH"HLN+ETHABF] HRL B,A CAME B,[%NEAHR"ETHER,,6] JRST ETHRAH LDB A,[%NEAH"PRO+ETHABF] LDB B,[%NEAH"PLN+ETHABF] HRL B,A CAME B,[%NETYP"IP,,4] JRST ETHRAP LDB A,[%NEAH"SPA0+ETHABF] ;Get source IP address LSH A,16. LDB B,[%NEAH"SPA1+ETHABF] IOR A,B ;BUG INFO,[ARP source addr ],OCT,A MOVE E,ETHATS ;Is the IP address in the table already? SOS E ETHRX1: JUMPL E,ETHRX2 ;Any more entries to check? LDB B,[%NEAT"PA+ETHAT(E)] ;BUG INFO,[ARP table entry ],OCT,E,[ of ],OCT,ETHATS,[ is ],OCT,B CAMN A,B ;Match? JRST ETHRX3 ; Yes - update it SOJA E,ETHRX1 ;Prev entry ETHRX2: MOVE E,ETHATS ;Not found in table - add new entry CAIL E,%NEATS ;Table full? JRST [ MOVEI E,1 ; Yes - flush it and start over MOVEM E,ETHATS ; FIXME remove a random entry instead SETZM E JRST ETHRX3] AOS ETHATS ETHRX3: DPB A,[%NEAT"PA+ETHAT(E)] ;Store IP address LDB C,[%NEAH"SHA01+ETHABF] ;Get sender's MAC LDB D,[%NEAH"SHA2+ETHABF] DPB C,[%NEAT"HA01+ETHAT(E)] ;Store MAC DPB D,[%NEAT"HA2+ETHAT(E)] BUG INFO,[Set ARP table entry ],OCT,E,[ to ip ],OCT,A,[ mac ],OCT,C,[ ],OCT,D LDB E,[%NEAH"OP+ETHABF] ;Get op ;BUG INFO,[ARP op ],OCT,E CAIE E,%NEAOP"REQ ;Is this a request? POPJ P, ;No - nothing more to do LDB B,[%NEAH"TPA+ETHABF] ;Get the target PA CAME B,[ETHUS] ;Is it our address? POPJ P, ;No - nothing more to do ;BUG INFO,[ARP reply] MOVEI E,%NEAOP"REPLY ;Construct reply packet DPB E,[%NEAH"OP+ETHABF] DPB A,[%NEAH"TPA+ETHABF] ;Set sender PA as target PA DPB B,[%NEAH"SPA1+ETHABF] ;Set our PA as source PA LSH B,-16. DPB B,[%NEAH"SPA0+ETHABF] MOVE E,C ;Reshuffle sender's MAC from 01/2 to 0/12 form LSH C,-16. LSH E,16. IOR D,E DPB C,[%NEAH"THA0+ETHABF] ;Set as target HA DPB D,[%NEAH"THA12+ETHABF] DPB C,[%NEH"DMAC0+ETHHBF] ;Set as destination MAC in header DPB D,[%NEH"DMAC12+ETHHBF] LDB C,[%NEAT"HA01+ETHME] ;Get our HA LDB D,[%NEAT"HA2+ETHME] DPB C,[%NEAH"SHA01+ETHABF] ;Set as source HA DPB D,[%NEAH"SHA2+ETHABF] MOVEI R,ETHABF ;Send it! MOVEI I,%NEAH"LENGTH JRST ETHTX ETHRAS: BUG INFO,[ARP packet too short, length ],OCT,I POPJ P, ETHRAH: BUG INFO,[ARP wrong hardware type ],OCT,B POPJ P, ETHRAP: BUG INFO,[ARP wrong protocol ],OCT,B POPJ P, ;Process an IPv4 packet. ;May clobber A-I, R. ; ETHRXI: PUSH P,Q ;IP can clobber all of these... PUSH P,J PUSH P,R PUSH P,W PUSH P,H MOVE A,I ;Compute number of words AOS A LSH A,-2 MOVE C,A ;BUG INFO,[IP ipgipt A=],OCT,A PUSHJ P,IPGIPT ;Get IP buffer -> A=datagram struct JRST ETHRX5 ; No buffers MOVE B,PK.BUF(A) ;IPGIPT does return this, but in BLKI form... BUG INFO,[IP rx allocated A=],OCT,A,[ B=],OCT,B HRL B,R ADDI C,-1(B) BLTUB B,(C) ;Copy packet MOVE B,C ;Words in datagram SETZM C ;Offset to header SETZM J ;Host table index of address (not used) PUSHJ P,IPRDGM ;BUG INFO,[IP rdgm done] ETHRX5: POP P,H POP P,W POP P,R POP P,J POP P,Q POPJ P, ;Send an IP packet (like IPKSNC for CHAOS). ;A contains datagram struct. ; ETHTXI: MOVE B,PK.DST(A) BUG INFO,[ethtxi A=],OCT,A,[ dest=],OCT,B POPJ P, ;Send an Ethernet packet (if possible). ;Unpacked header is in ETHHBF. R contains pointer to unpacked data. ;I contains data length in bytes. ; ;FIXME IFN UNAP,... ETHTX: BUG INFO,[ethtx ptr ],OCT,R,[ len ],OCT,I PUSHJ P,UNAFF ;Find a free transmit buffer POPJ P, ;None left. Give up. ;BUG INFO,[ethtx buf ],OCT,B,[ desc ],OCT,D,[ len ],OCT,I MOVE TT,B ;Convert header HRLI TT,ETHHBF BLTBU TT,<%NEH"WORDS-1>(B) ADDI B,%NEH"WORDS ;Convert data MOVE TT,B HRL TT,R MOVE A,I ADDI A,3 LSH A,-2 ADD A,B MOVE C,TT ;XXX ;BUG INFO,[ethtx data bltbu tt ],OCT,C,[ a ],OCT,A BLTBU TT,(A) ADDI I,%NEH"LENGTH-%NEH"PAD ;Add length of header ;BUG INFO,[ethtx end desc ],OCT,D,[ len ],OCT,I JRST UNATX ;Transmit it ;Check whether the interface is able to send a packet. ;Skip-return if it is. ; ETHCTS: BUG INFO,[ethcts] ;FIXME check if we have an ARP entry, and if not, send a request AOS (P) POPJ P, IFN UNAP,[ ;;; DEUNA/DELNA driver $INSRT DEUNA %ETMTU==1492. ;MTU (could be 1500, but be conservative) %ETMXL==(1500./4) ;Maximum packet length in 32-bit words EBLK ;FIXME This memory stuff would be simpler given a routine ;that allocated and mapped a page, returning the PDP-10 ;address and the two Unibus addresses. We could then ;allocate on the fly while setting up the rings. ;FIXME The setup stuff might also be cleaner with byte ptrs as above. ;FIXME Or should we just use IP buffers for all of this? They seem to be ;half-page-aligned... UNPAGS: BLOCK %UNNPG ;PDP-10 pages: 0,,addr UNPAGU: BLOCK %UNNPG ;Unibus pages: first-addr,,second-addr UNTXRP: BLOCK %UNNTB ;Transmit ring: descriptor addr,,buffer addr UNRXRP: BLOCK %UNNRB ;As above for receive ;(setup code assumes those two are contiguous) UNTXRI: 0 ;Transmit ring index (next free slot) UNRXRI: 0 ;Receive ring index (next used slot) UNADNI: CPOPJ ;Call this after a DNI interrupt UNARCB: 0 ;Have we seen an RCBI interrupt? ;FIXME A flags word would make more sense (if we ever need a second flag!) ;FIXME For horrible hack below BLOCK 2000-<.&1777> ;Align to a page boundary UNPGHK: BLOCK 2000*%UNNPG BBLK ;BUG INFO the UNA's registers. UNREGS: PUSH P,A PUSH P,B PUSH P,C PUSH P,D IORDI A,%UNR0 IORDI B,%UNR1 IORDI C,%UNR2 IORDI D,%UNR3 BUG INFO,[UNA PCSR0=],OCT,A,[ PCSR1=],OCT,B,[ PCSR2=],OCT,C,[ PCSR3=],OCT,D POP P,D UNR3: POP P,C POP P,B POP P,A POPJ P, ;BUG INFO the PCB. UNDPCB: PUSH P,A PUSH P,B PUSH P,C MOVE A,UNPAGS MOVE B,0(A) MOVE C,1(A) BUG INFO,[UNA PCB:],OCT,B,[ ],OCT,C JRST UNR3 ;Initialise the DEUNA. ; UNAINI: MOVEI D,%UNNPG-1 UNAIN1: IFN 0,[ ;FIXME This causes the core job to crash later... PUSHJ P,TCALL ;Allocate a page of unshuffleable low core JRST IOMQ BUG HALT,[Couldn't allocate page for DEUNA] MOVEI B,MUETH ;Tag what it's being used for DPB B,[MUR,,MEMBLT(A)] LSH A,10. ;Save the pointer ] IFE 0,[ MOVEI A,2000 ;Use statically-allocated block above IMUL A,D ADDI A,UNPGHK ] MOVEM A,UNPAGS(D) MOVE B,A LSH B,-9. ;DEC page number ;FIXME Could we use %UQFST? (How do we know nobody else is?) ; Could have a word with either 0 or the MU in... IORI B,%UQVAL PUSHJ P,UBAASL ;Allocate first UBA paging slot IOWRI B,UBAPAG(A) HRLZ C,A PUSHJ P,UBAASL ;Allocate second slot AOS B IOWRI B,UBAPAG(A) HRR C,A LSH C,11. MOVEM C,UNPAGU(D) MOVE A,UNPAGS(D) ;BUG INFO,[UNA block ],OCT,D,[ entry ],OCT,B,[ pdp10 ],OCT,A,[ unibus ],OCT,C SOJGE D,UNAIN1 ;Initialisation sequence from DELUA user guide, page 3-8 (42). ;This needs us to wait for an interrupt in several places, so ;UNACMD has magic interrupt-continuation behaviour. ;Enable interrupts. MOVEI A,%UN0"INTE IOWRI A,%UNR0 ;Set the PCB address. HLRZ A,UNPAGU ;Low bits IOWRI A,%UNR2 LSH A,-16. ;High bits IOWRI A,%UNR3 MOVEI A,%UN0"DNI+%UNCMD"GETPCB ;Ack DNI as it's on after reset PUSHJ P,UNACMD ;We've really returned to the caller here ;FIXME put IP address in too, use in ARP above ;Get the MAC address for ARP to use MOVE A,UNPAGS MOVSI TT,%UNAF"RDDPA MOVEM TT,0(A) MOVEI A,%UNCMD"GETCMD PUSHJ P,UNACMD PUSHJ P,UNDPCB ;Show results MOVEI TT,ETHHBF ;Unpack MAC address into ETHHBF HRL TT,UNPAGS BLTUB TT,ETHHBF+1 MOVE TT,[.BP <%NE16_4.>,ETHHBF] LDB A,TT LSH A,16. ILDB B,TT IOR A,B ;BUG INFO,[my MAC 01 =],OCT,A DPB A,[%NEAT"HA01+ETHME] ILDB A,TT ;BUG INFO,[my MAC 2 =],OCT,A DPB A,[%NEAT"HA2+ETHME] ;Set up transmit and receive rings MOVE D,UNPAGS ;Point to TDR (RDR follows) ADDI D,<%UNTDR/4> SETZM C ;Buffer index UNAIN2: MOVE TT,C ;Work out index into UNPAGS/UNPAGU LSH TT,-1 AOS TT HLRZ B,UNPAGU(TT) ;Get Unibus addr. Even on the left... TRNE C,1 HRRZ B,UNPAGU(TT) ;... and odd on the right ADDI B,%UNBOF ;Add word-aligning padding at start ;FIXME This bit for now, until I rewrite this whole section.. HRLM D,UNTXRP(C) ;Descriptor pointer MOVE A,UNPAGS(TT) TRNE C,1 ADDI A,1000 HRRM A,UNTXRP(C) ;Data pointer MOVE A,UNTXRP(C) ;BUG INFO,[ring],OCT,C,[ptr],OCT,A MOVSI A,%UNBSZ ;SLEN (only used for recv) HRR A,B ;SEGB low bits MOVEM A,0(D) ;BUG INFO,[t/rdr ],OCT,C,[ at ],OCT,D,[ 0/1 = ],OCT,A LSH B,-16. ;SEGB high bits CAIL C,%UNNTB ;in the receive ring? IORI B,%UNTD"OWN ; yes - give this buffer to DEUNA HRLZS B ;zero other flags MOVEM B,1(D) ;BUG INFO,[t/rdr ],OCT,C,[ at ],OCT,D,[ 2/3 = ],OCT,B ADDI D,2 ;Next descriptor AOS C ;Next buffer CAIGE C,<%UNNTB+%UNNRB> JRST UNAIN2 ;Set ring descriptor format MOVSI A,%UNAF"WRDRF ;Set up for UDB-using command PUSHJ P,UNAPCB HLRZ B,UNPAGU ;Get Unibus address of TDR ADDI B,%UNTDR HRLZ A,B ;TDRB low bits IORI A,<4_8> ;TELEN LSH B,-16. IOR A,B ;TDRB high bits MOVEM A,0(D) ;Store word 0/1 ;BUG INFO,[rdf 0/1 =],OCT,A MOVSI A,%UNNTB ;TRLEN HLRZ B,UNPAGU ;Get Unibus address of RDR ADDI B,%UNRDR HRR A,B ;RDRB low bits MOVEM A,1(D) ;Store word 2/3 ;BUG INFO,[rdf 2/3 =],OCT,A LSH B,-16. ;RDRB high bits IORI B,<4_8> ;RELEN HRLZS B IORI B,%UNNRB ;RRLEN MOVEM B,2(D) ;Store word 4/5 ;BUG INFO,[rdf 4/5 =],OCT,B MOVEI A,%UNCMD"GETCMD PUSHJ P,UNACMD ;Start the interface MOVEI A,%UNCMD"START PUSHJ P,UNACMD BUG INFO,[DEUNA setup done] MOVEI A,CPOPJ ;Reset UNADNI behaviour MOVEM A,UNADNI POPJ P, ;Set up the PCB for a command that uses the UDB. ;A=Unibus word 0 (command),,LH of Unibus word 3 (0 for most commands). ;Returns: D pointing to UDB. ; UNAPCB: HLRZ B,UNPAGU ;Get Unibus address of UDB ADDI B,%UNUDB MOVE D,UNPAGS ;Point to PCB MOVE C,B ;UDBB low bits HLL C,A ;Command MOVEM C,0(D) ;Store word 0/1 HRRZS A ;(command-specific) LSH A,8. LSH B,-16. ;UDBB high bits IOR A,B HRLZS A ;Zero word 3 MOVEM A,1(D) ;Store word 2/3 MOVE D,UNPAGS ;Point to UDB ADDI D,<%UNUDB/4> POPJ P, ;Send a command (in A). Then set the next instruction in the caller as UNADNI, ;and return to the caller's caller -- this means that it'll appear to return ;(in interrupt context) when the command is done. ; UNACMD: POP P,UNADNI ;Back to the caller when DNI is set IORI A,%UN0"INTE ;Keep interrupts on IOWRI A,%UNR0 ;Send command POPJ P, ;Return to caller's caller ;Handle a DEUNA interrupt. ; ;FIXME Should this use a low-priority interrupt for the actual copying? ;(as CHAOS does) EBLK UNABRK: 0 BBLK JSR UTCSAV IORDI H,%UNR0 ;Read status ;BUG INFO,[DEUNA interrupt, PCSR0=],OCT,H,[ UNADNI=],OCT,UNADNI TRNE H,%UN0"SERI+%UN0"PCEI+%UN0"FATL+%UN0"USCI BUG HALT,[DEUNA error, PCSR0=],OCT,H ;FIXME do something better! TRNE H,%UN0"RXI PUSHJ P,UNARX ;FIXME %UN0"TXI TRNE H,%UN0"RCBI SETOM UNARCB ;Just remember for when a buffer is freed MOVE A,H ANDI A,%UN0"ALLINT+%UN0"INTE IOWRI A,%UNR0 ;Acknowledge interrupts TRNE H,%UN0"DNI ;DNI handler *after* acknowledgement PUSHJ P,@UNADNI ;(because it will probably issue a new cmd) JRST DSKEX ;Return from interrupt ;Check the receive ring for new packets, and process them. ;Ideally, we want to process packets in the same order that the DEUNA is ;writing them to the ring -- but just in case we get out of sync, we check the ;whole ring each time, starting with the first empty slot (UNRXRI) we found ;last time. ; UNARX: MOVSI W,<-%UNNRB> ;W = -Number to check,,buffer index HRR W,UNRXRI SETOM UNRXRI ;Haven't found an empty buffer yet UNARX1: HRRZ A,W ;Wrap index around CAIL A,%UNNRB HLLZS W HLRZ J,UNRXRP(W) ;Descriptor MOVE Q,1(J) ;Is there a packet in this buffer? (OWN=0) TLNE Q,%UNTD"OWN JRST [ SKIPGE UNRXRI ;No. First empty buffer we've found? HRRZM W,UNRXRI ; Yes, start here next time JRST UNARX2] TLNE Q,%UNTD"STF ;STF+ENF should always be true, but check... TLNN Q,%UNTD"ENF JRST UNARSF TLNE Q,%UNTD"ERRS+%UNTD"OFLO JRST UNARER HRRZ R,UNRXRP(W) ;Buffer LDB I,[.BP 7777,1(J)] ;Get MLEN SUBI I,4 ;Ignore CRC at end PUSHJ P,ETHRX UNARX3: TLO Q,%UNTD"OWN ;Set OWN=1 to mark as free MOVEM Q,1(J) SKIPE UNARCB ;Have we had an RCB interrupt? JRST [ SETZM UNARCB ;Yes. Tell the DEUNA there's a free buffer now. MOVEI A,%UN0"INTE+%UNCMD"PDMD IOWRI A,%UNR0 JRST UNARX2] UNARX2: AOBJN W,UNARX1 ;Repeat if we haven't looked at all buffers SKIPGE UNRXRI ;Repeat if we haven't found a free buffer JRST UNARX1 ; FIXME: Or maybe HRRZM W,UNRXRI ? POPJ P, ;These are only here because you can't use BUG inside []... UNARSF: BUG INFO,[DEUNA split frame ],OCT,Q JRST UNARX3 UNARER: BUG INFO,[DEUNA frame with errors ],OCT,Q JRST UNARX3 ;Find an empty buffer in the transmit ring. ;On success, skip return with descriptor in D, buffer in B. ;On failure (the ring is full), normal return. ;Clobbers A-TT. ; UNAFF: MOVSI E,<-%UNNTB> ;E = -Number to check,,buffer index HRR E,UNTXRI UNAFF1: HRRZ A,E ;Wrap index around CAIL A,%UNNTB HLLZS E HLRZ D,UNTXRP(E) ;Descriptor MOVE A,1(D) ;Is this buffer free? (OWN=0) ;BUG INFO,[unaff checking slot ],OCT,E,[ status ],OCT,A TLNE A,%UNTD"OWN JRST [ AOBJN E,UNAFF1 ;Try the next one POPJ P,] ;No buffers free. HRRZM E,UNTXRI ;Yes. Start at this buffer + 1 next time. AOS UNTXRI ; (Doesn't matter if it wraps.) HRRZ B,UNTXRP(E) ;Buffer ;BUG INFO,[unaff found free buffer ],OCT,E,[ desc ],OCT,D,[ buff ],OCT,B AOS (P) ;Skip return POPJ P, ;Transmit a packet from a descriptor obtained by UNAFF. ;Descriptor in D, data length in bytes in I. ;Clobbers A. ; UNATX: ;BUG INFO,[unatx desc ],OCT,D,[ length ],OCT,I HRLM I,0(D) ;Set SLEN MOVSI A,%UNTD"OWN+%UNTD"STF+%UNTD"ENF IORM A,1(D) ;Set flags MOVEI A,%UN0"INTE+%UNCMD"PDMD ;Tell the DEUNA to check the ring IOWRI A,%UNR0 POPJ P, ;FIXME ;Stylewise, trying to match IMP... ;See: ;receive packet - IMPRM (uses IPGIPT, IPRDGM) ;send packet - IMPOB0 (uses IPGIOQ) ] ;UNAP