; PupAlEtha.asm - Pup Alto Ethernet driver (assembly-language code)
; Contains initial portion of Ethernet interrupt handler, plus
; StartEther procedure.  These are hand-coded to minimize
; turnaround time for the half-duplex Alto Ethernet interface.
; Copyright Xerox Corporation 1979, 1980

; Last modified March 9, 1980  5:15 PM by Taft

.ent etherProlog
.ent StartEther
.ent EtherPupFilter

.bextz EtherInterrupt
.bext pbiFreeQ
.bext lenEtherPacket

.srel

etherProlog: .etherProlog
StartEther: .StartEther
EtherPupFilter: .EtherPupFilter

.zrel

EtherInterruptEntry:  .EtherInterruptEntry

.nrel

; *** constants duplicated in PupAlEth.decl ***

packetLength = 6	; offset of PBI.packetLength
etherPacket = 7		; PBI offset of first word of packet
etherType = 8.		; offset of EtherPBI.type
pupLength = 9.		; offset of PBI.pup.length
typePup = 1000		; ethernet type meaning "I'm a Pup"

; *** NDB offsets - see EtherNDB structure in PupAlEth.decl ***

lastEPLoc = 14.		; EPLoc for most recent interrupt
lastEELoc = 15.		; EELoc for most recent interrupt
lastEIB = 16.		; -> input PBI for most recent interrupt 
eIB = 17.		; -> input PBI or 0 if none
eOB = 18.		; -> output PBI or 0 if none
eState = 19.		; -1: xmting, 0: off, 1: rcving
ePLoc = 20.		; -> Post location
eBLoc = 21.		; -> Interrupt bit mask location
eELoc = 22.		; -> EOT count location, Posted
eLLoc = 23.		; -> Load location (mask)
eICLoc = 24.		; -> Input count location
eIPLoc = 25.		; -> Input pointer location
eOCLoc = 26.		; -> Output count location
eOPLoc = 27.		; -> Output pointer location
eHLoc = 28.		; -> Host Address location
outCmd = 29.		; SIO bits to start transmitter
inCmd = 30.		; SIO bits to start receiver
resetCmd = 31.		; SIO bits to reset hardware & firmware
; prolog in 32.-38.
save3 = 38.		; ac3 temp
savedID = 39.		; entry to normal bcpl interrupt handler
stack = 40.		; ac2 temp

; The state of the Ethernet interface is represented as follows:
; if eState is zero, the interface is idle;  if nonzero, active.
; 1 means the receiver has been started;  -1 means the transmitter.
; If eIB is nonzero, an input buffer is set up.
; If eOB is nonzero, an output buffer is set up.
; If an input or output buffer is not set up, then eICLoc or eOCLoc
; (respectively) must be zero.
; When the interface is started, ePLoc is set to zero.  Hence
; eState nonzero and ePLoc zero means that the interface is
; presently turned on and has not yet posted.
; When the receiver is started, the first word of the input packet
; buffer is zeroed.  The idea is that if a packet starts to arrive,
; it will set this word nonzero, thereby indicating that receipt
; of a packet is in progress.


; Ethernet interrupt prolog -- copied into each NDB
; Must match prolog field in EtherNDB

.etherProlog:
; EtherNDB.asmProlog:
	sta 3 .+6		; save3
	lda 3 .+4		; ndb
	jmp @EtherInterruptEntry

; EtherNDB.bcplProlog:
	lda 0 .+2		; ndb
	jmp @EtherInterrupt

; The NDB for the interrupting interface is in AC3.
; The former contents of ac3 is in the ndb.
; After saving all necessary state for the current Ethernet post
;  condition, we attempt to get the receiver turned on as
;  quickly as possible, then branch to the normal (BCPL) interrupt
;  handler for more leisurely cleanup.
; ** Must be careful not to clobber the carry bit **

.EtherInterruptEntry:		; AC3/ ndb
	sta 0 save0
	lda 0 @ePLoc 3		; Ethernet post word
	sta 0 lastEPLoc 3	; Save for later
	snz 0 0			; Has Ethernet posted?
	 jmp ethin6		; No, manually-initiated interrupt
	sta 2 save2
	lda 2 c177400		; Post code = 0 (input done)?
	and 2 0 szr
	 jmp ethin1		; No, can re-use input buffer
	lda 2 eIB 3		; Yes, get eIB just completed
	sta 2 lastEIB 3		; Save it away
	lda 2 @eELoc 3		; Save ending word count also
	sta 2 lastEELoc 3
	jmp ethin2

ethin1:	lda 2 eIB 3		; Get current input pbi
	sz 2 2			; Skip if none set up
	 jmp ethin3		; Have one, go use buffer
ethin2:	lda 2 @.pbiFreeQ	; None yet, get pbiFreeQ header
	lda 0 0 2		; Get pointer to head pbi
	snz 0 0			; Skip if have one
	 jmp ethin4		; No pbi's available
	sta 0 eIB 3		; Got one, store pointer
	lda 0 @eIB 3		; Get successor
	sta 0 0 2		; Put at head of pbiFreeQ
	lda 2 eIB 3
	lda 0 .etherPacket	; Offset of packet in pbi
	add 2 0			; Make pointer to packet
	sta 0 @eIPLoc 3		; Store for interface
	lda 0 @.lenEtherPacket	; Maximum packet length
	sta 0 @eICLoc 3

; AC2/ pbi to use for input.
ethin3:	subc 0 0		; won't clobber carry bit
	sta 0 etherPacket 2	; Zero first word of packet
	sta 0 @ePLoc 3		; Zero post location
	lda 0 inCmd 3
	sio			; Start up input
	lda 0 c1		; Set state to "Receiver on"

; Now continue on to the normal BCPL interrupt entry sequence.
; Note that this piece of code is invisible to EtherInterrupt,
;  which returns directly to the interrupt handler.
ethin5:	sta 0 eState 3		; Update software state
	lda 2 save2
ethin6: lda 0 savedID 3		; Interrupt handler entry address
	sta 0 ethin7		; ----+
	lda 0 save0		;     |
	lda 3 save3 3		;     |
	jmp @.+1		;     |
ethin7:	 0			; <---+

; Here if no input pbi and pbiFreeQ empty.  AC0/ 0
ethin4:	sta 0 @ePLoc 3		; Zero post location
	sta 0 eIB 3		; Note no buffer set up now
	sta 0 @eICLoc 3		; Prevent input-under-output
	jmp ethin5

save0:			0
save2:			0

; StartEther(ndb)
; Called from interrupt level after processing of a packet is
; complete and new input and/or output packets have been set up.
; This procedure ensures that the transmitter is turned
; on if there is a pbi to be sent and there is not already a packet
; pouring into the receiver.  If there is no output, it turns on
; the receiver if necessary.  It is assumed that the interface is
; already on if eState is nonzero.

.StartEther:
	sta 3 1 2		; Return address
	mov 0 3			; ndb
	sta 2 stack 3		; Need two index registers
	lda 1 eState 3		; Get current software state
	movl# 1 1 szc		; Already transmitting?
	 jmp return		; Yes, nothing to do
	lda 0 resetCmd 3	; Constant used below
	lda 2 eOB 3		; Is there an output pbi set up?
	snz 2 2
	 jmp start4		; No, go check input
	mov 1 2 snr		; Yes, is receiver already on?
	 jmp start2		; No, go start transmitter

; We want to transmit but the receiver is now on.
; Check whether a packet is now being received, and do not
; disturb the interface if so.
	lda 1 @eBLoc 3		; Save mask for later restoration
	lda 2 @eIPLoc 3		; Check first word of packet
	lda 2 0 2
	sz 2 2			; Packet now being received?
	 jmp return		; Yes, defer output

; Reset interface and start transmitter.
; We must be careful not to cause an interrupt.
; ac0/ etherResetCommand, ac1/ etherMask, ac2/ 0
	sta 2 @eBLoc 3		; Zero interrupt bit mask location
	sio			; Reset the interface
	sta 1 @eBLoc 3		; Put back regular mask
start2:	sta 2 @ePLoc 3		; Zero post location
	sta 2 @eLLoc 3		; Initialize load to zero
	lda 0 outCmd 3
	sio			; Start transmitter
	mkminusone 0 0		; Note that we are now transmitting
	jmp start5

; Here if there is no output to be sent.
; See whether we need to turn on the receiver.
; ac1/ software state
start4:	lda 2 eIB 3		; Get current input buffer
	snz 1 1			; Is interface already on?
	snz 2 2			; No, is there an input buffer?
	 jmp return		; Already on or no buffer, done

; The interface is now off and an input buffer is set up.
; Start the receiver.  ac1/ 0, ac2/ pbi
	sta 1 etherPacket 2	; Zero first word of packet
	sta 1 @ePLoc 3		; Zero post location
	lda 0 inCmd 3
	sio			; Start up input
	mkone 0 0		; Note that we are now receiving
start5:	sta 0 eState 3

return:	lda 2 stack 3
	lda 3 1 2
	jmp 1 3

.pbiFreeQ:	pbiFreeQ
.etherPacket:	etherPacket
.lenEtherPacket: lenEtherPacket
c1:		1
c177400:	177400


; EtherPupFilter(pbi)
; hand-coded to avoid procedure call overhead at interrupt level
;  = (pbi>>PBI.pup.length+5) rshift 1 eq pbi>>PBI.packetLength &
;     pbi>>EtherPBI.type eq typePup

.EtherPupFilter:
	sta 3 1 2
	mov 0 3
	lda 0 pupLength 3
	lda 1 c5
	addzr 1 0
	lda 1 packetLength 3
	se 0 1
	 jmp rfalse
	lda 0 etherType 3
	lda 1 .typePup
	se 0 1
rfalse:	 mkzero 0 0 skp
	 mkminusone 0 0
	lda 3 1 2
	jmp 1 3

c5:	5
.typePup: typePup


.end