1
0
mirror of https://github.com/PDP-10/its.git synced 2026-03-20 16:38:16 +00:00
Files
PDP-10.its/src/system/ether.1
2018-08-16 21:14:48 +01:00

637 lines
16 KiB
Groff

;;; ITS Ethernet support -*-MIDAS-*-
;;;
;;; Copyright (C) 2018 Adam Sampson <ats@offog.org>
;;;
;;; 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 <https://www.gnu.org/licenses/>.
;;; Generic Ethernet support
%NE32==37777777777
%NE16==177777
%NE8==377
.BEGIN %NEH ;Fields in the Ethernet header
PAD==2 ;padding at start of buffer
LENGTH==<PAD+6+6+2>
WORDS==<LENGTH/4>
;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==<<LENGTH+3>/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
IPOGO: BUG INFO,[ipogo]
POPJ P,
IPKSNE: BUG INFO,[ipksne]
POPJ P,
;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,<ETHHBF+%NEH"WORDS-1>
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,<ETHABF+%NEAH"WORDS-1>
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.
;
ETHRXI:
BUG INFO,[IP]
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:
;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.
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
;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