; DLSDriverA.asm -- Alto DLS driver, assembly-language portion

; Last modified May 2, 1982  3:17 PM by Taft
; Last modified November 16, 1983  10:20 AM by Diebert

.bext dlsOnVector
.bext lvUserFinishProc
.bext dlsSavedUserFinish
.bext lvSwatContextProc
.bext dlsSavedSwatContext
.bextz inputRBCB
.bextz outputRBCB
.bextz baseIGCB
.bextz lbTable
.bext Block
.bext dlsInputOverflows
.bext dlsErrorInterrupts

.ent SendXon
.ent TurnOnDLS
.ent TurnOffDLS
.ent DLSUserFinishProc
.ent DLSSwatContextProc
.ent DLSInputInterrupt
.ent DLSOutputInterrupt
.ent DLSErrorInterrupt
.ent StartDLSOutput
.ent DLSInputEmpty
.ent DLSInputIdle
.ent DLSOutputEmpty
.ent DLSOutputFull
.ent AndMemory
.ent OrMemory
.ent WaitForBitTimes
.ent InitRingBuffer
.ent ResetRingBuffer
.ent ReadRingBuffer
.ent WriteRingBuffer
.ent ReadAC1

.srel

ReadAC1: .ReadAC1
TurnOnDLS: .TurnOnDLS
SendXon: .SendXon
TurnOffDLS: .TurnOffDLS
DLSUserFinishProc: .DLSUserFinishProc
DLSSwatContextProc: .DLSSwatContextProc
DLSInputInterrupt: .DLSInputInterrupt
DLSOutputInterrupt: .DLSOutputInterrupt
DLSErrorInterrupt: .DLSErrorInterrupt
StartDLSOutput: .StartDLSOutput
DLSInputEmpty: .DLSInputEmpty
DLSInputIdle: .DLSInputIdle
DLSOutputEmpty: .DLSOutputEmpty
DLSOutputFull: .DLSOutputFull
AndMemory: .AndMemory
OrMemory: .OrMemory
WaitForBitTimes: .WaitForBitTimes
InitRingBuffer: .InitRingBuffer
ResetRingBuffer: .ResetRingBuffer
ReadRingBuffer: .ReadRingBuffer
WriteRingBuffer: .WriteRingBuffer

.nrel

; Special emulator instructions
dlson = 61016		; Load pointers and turn on DLS
dlsoff = 61015		; Turn off DLS
qch = 61022		; Queue character for output on line
gcrb = 61017		; Get character from DLS ring buffer
andm = 61007		; And to memory  @ac1 = @ac1&ac0
orm = 61023		; Or to memory  @ac1 = @ac1%ac0
setblv = 61025		; Set Boot Locus Vector to value in ac0
rrb = 61026		; Read Ring Buffer
wrb = 61027		; Write Ring Buffer

; Offsets in structure DLB, copied from DLSDriver.decl
DLB.line = 0		; Line number
DLB.outActive=11.	; B15=1 if output is active
DLB.flowControl=12.	; non-zero if flow control is enabled
DLB.flowControlAction=13.	; B15=1 if output is active
DLB.oRBD=14.		; Output ring buffer descriptor
DLB.iRBD=18.		; Input ring buffer descriptor
  DLB.iRBD.length = 19.	; Input buffer length
DLB.oLCB=22.		; Output line control block
  DLB.oLCB.line=23.	; Word containing line number in right byte
DLB.iLCB=26.		; Input line control block
  DLB.iLCB.interval=29.	; Word containing interval in right byte

; Hardware definitions, from DLSDriver.decl
IGCB.link = 0
IGCB.typeLine = 1
IGCB.idle = 2
IGCB.interval = 3
IGCB.active = 4

; RingBufferDescriptor, used by RingBytes and by microcode.
RBD.begin = 0	; pointer to beginning of ring buffer
RBD.length = 1	; length of ring buffer in bytes
RBD.read = 2	; index of last byte read
RBD.write = 3	; index of last byte written

; ReadAC1()
.ReadAC1:
	mov 1 0
	jmp 1 3


; TurnOnDLS()
; Called both from DLS initialization and when leaving Swat

.TurnOnDLS:
	lda 0 @lvdlsOnVector
	dlson
	jmp 1 3

lvdlsOnVector: dlsOnVector


; TurnOffDLS()
; Called when entering Swat

.TurnOffDLS:
	dlsoff
	jmp 1 3


; DLSUserFinishProc(OsFinishCode)
; Called to clean up during a finish

.DLSUserFinishProc:
	sta 3 1 2
	dlsoff			; Shut off the DLS
	adczl 0 0		; Set boot locus vector to 177776
	setblv			;  (start all tasks except emulator in rom)
	subzr 0 0		; Silent boot
	sio
	lda 3 @lvlvSwatContextProc  ; Get ptr to SwatContextProc
	lda 0 @lvdlsSavedSwatContext  ; Restore old SwatContextProc
	sta 0 0 3
	lda 3 @lvlvUserFinishProc  ; Get ptr to UserFinishProc
	lda 0 @lvdlsSavedUserFinish  ; Restore old UserFinishProc
	sta 0 0 3
	lda 3 1 2
	jmp 1 3

lvlvSwatContextProc: lvSwatContextProc
lvdlsSavedSwatContext: dlsSavedSwatContext
lvlvUserFinishProc: lvUserFinishProc
lvdlsSavedUserFinish: dlsSavedUserFinish


; DLSSwatContextProc(direction)
; Routine called when entering and leaving Swat
; Direction is 0 when entering and -1 when leaving

.DLSSwatContextProc:
	snz 0 0			; Entering or leaving?
	 jmp .TurnOffDLS	; Entering Swat, turn off DLS
	jmp .TurnOnDLS		; Leaving Swat, turn on DLS

; DLSInputInterrupt()

c377:	377
.DLSInputInterrupt:
	sta 3 1 2		; Save return
iint1:	lda 0 inputRBCB		; Input ring buffer control block
	gcrb			; Get item from buffer (char,,line)
	 jmp iint99		; No more
	movs 0 1		; Get char in rh
	lda 3 c377
	and 3 0			; Mask line number (line# in AC0)
	and 3 1			; Mask char (char in AC1)
	lda 3 lbTable		; Get base of DLB table
	add 0 3			; Get pointer to line's DLB
	lda 3 0 3		; AC3 contains addr of DLB

	lda 0 DLB.flowControl 3	; Offset to flowControl word
	mov 0 0 szr
	 jmp iint7		; In flowControl mode

; This is non flow controlled path!
	lda 0 .DLB.iRBD		; Compute pointer to input RBD
	add 3 0
	wrb			; Write char into ring buffer
iint3:	 isz @lvdlsInputOverflows  ; Full, count input buffer overflows
	 jmp iint1		; Ok, done, look for more input
	jmp iint1

; This is flow controlled path!
iint7:	lda 0 cXoff		; Check for Xoff
	subs 1 0
	movzl 0 0 szr
	 jmp iint5		; not Xoff

; Char is Xoff
	lda 0 fcStop
	lda 1 .DLB.flowControlAction	; set up offset for action bits
	add 3 1			; AC1 ← adr of Action
	orm			; Set stop bit
	lda 1 cXoff		; put the Xoff back in the buffer
	jmp iint12

iint17:	lda 0 fcResetStopTimerGoing	; Xon
	lda 1 .DLB.flowControlAction
	add 3 1
	andm			; reset stop and timer Going
	lda 1 cXon
	jmp iint12
fcResetStopTimerGoing: 157775
.DLB.flowControlAction: DLB.flowControlAction
.DLB.iRBD: DLB.iRBD

iint5:	lda 0 cXon		; Check for Xon
	subs 1 0
	movzl 0 0 snr
	 jmp iint17		; is Xon

iint12:	lda 0 .DLB.iRBD		; Compute pointer to input RBD
	add 3 0
	wrb			; Write char into ring buffer
	 jmp iint3		; Overflow

	lda 1 .minBuffLeft	; Check to see how much space is left
	adcl# 0 1 szc		; skip if not enough room
	 jmp iint1		; See if more to do

	lda 1 DLB.flowControlAction 3	; put action word in AC1
	movr 1 1 szc		; check for XoffSent.
	 jmp iint1		; has been sent

; This point we don't have room in buffer so force Xoff to be sent
	lda 0 DLB.outActive 3	; put outActive in AC0
	movr# 0 0 snc
	 jmp iint15		; Not Active

; Active set send in action
	lda 0 fcSendXoff
	lda 1 .DLB.flowControlAction	; set up offset for action bits
	add 3 1			; AC1 ← adr of Action
	orm			; Set stop bit
	jmp iint1		; see if more to do.

iint15:				; not active so send char
	lda 0 fcXoffSent
	lda 1 .DLB.flowControlAction	; set up offset for action bits
	add 3 1			; AC1 ← adr of Action
	orm			; Set XoffSent bit
	lda 1 cXoff
	isz DLB.outActive 3	; dlb>>DLB.outActive=true
	lda 0 .DLB.oLCB.line	; Get offset of output LCB
	add 3 0			; Make pointer for microcode
	qch			; Queue character for output
	jmp iint1		; go see if more to do.

iint99:	lda 3 1 2
	jmp 1 3	

 

lvdlsInputOverflows: dlsInputOverflows
lvdlsErrorInterrupts: dlsErrorInterrupts

; DLSErrorInterrupt()

.DLSErrorInterrupt:
	isz @lvdlsErrorInterrupts  ; Just count them
	 jmp 1 3
	jmp 1 3


oint99:	lda 3 1 2
	jmp 1 3

; DLSOutputInterrupt()

.DLSOutputInterrupt:
	sta 3 1 2		; Save return
oint1:
	lda 0 outputRBCB	; Output ring buffer control block
	gcrb			; Get item from buffer (ptr to OLCB.line)
	 jmp oint99		; No more (exit)
	mov 0 3			; Save ptr to DLB.oLCB.line
	dir
	lda 0 DLB.flowControl-DLB.oLCB.line 3	; AC0 ← flowControl
	mov 0 0 szr
	 jmp oint5		; flowControl enabled

oint7:	lda 0 .oRBDmoLCB.line	; Compute pointer to output RBD
	add 3 0
	rrb			; Get char from ring buffer
	 jmp oint2		; Empty, go clear outActive flag
	eir
	mov 3 0			; Ptr to DLB.oLCB.line
	qch			; Queue character for output
	jmp oint1		; Look for more work to do

oint5:				; At this point we have flow control enabled and we are
				; ready to send a char to this line.
	lda 0 DLB.flowControlAction-DLB.oLCB.line 3	; AC0 ← flowControlAction
	movl 0 0 szc		; check to see if sendXoff is on. skip if off
	 jmp oint10		; sendXoff is on!
	movl 0 0 szc		; check to see if sendXon is on. skip if off
	 jmp oint15		; sendXon is on!
	movl 0 0 snc		; skip if Stop flag on
	 jmp oint7		; go ahead.

; Here if output buffer now empty
oint2:	dsz DLB.outActive-DLB.oLCB.line 3  ; dlb>>DLB.outActive=false
	 nop
	eir
	jmp oint1		; Look for more work to do

oint10:				; here we are. we need to send an Xoff
	lda 1 .flowControlActionmoLCB.line	; set Xoff sent
	add 3 1
	lda 0 fcXoffSent
	orm			; this turns on XoffSent
	lda 0 fcResetSendXoff
	andm
	eir
	lda 1 cXoff
	mov 3 0			; Ptr to DLB.oLCB.line
	qch			; Queue character for output
	jmp oint1		; Look for more work to do

oint15:				; here we are. we need to send an Xon
	lda 1 .flowControlActionmoLCB.line	; reset Xoff sent
	add 3 1
	lda 0 fcResetSendXonXoffSent
	andm
	eir
	lda 1 cXon
	mov 3 0			; Ptr to DLB.oLCB.line
	qch			; Queue character for output
	jmp oint1		; Look for more work to do

c360:	360
cXoff:	223
cXon:	21
fcResetXoffSent: 177776
fcStop:	20000
fcResetStop: 157777
fcXoffSent: 1
fcSendXoff: 100000
fcResetSendXoff: 77777
fcSendXon: 40000
fcResetSendXonXoffSent: 137776
.minBuffLeft: 6
.DLB.flowControl: DLB.flowControl
.DLB.oRBD: DLB.oRBD
.DLB.oLCB.line: DLB.oLCB.line
.oRBDmoLCB.line: DLB.oRBD-DLB.oLCB.line
.flowControlActionmoLCB.line: DLB.flowControlAction-DLB.oLCB.line

; StartDLSOutput(dlb)

.StartDLSOutput:
	dir
	sta 3 1 2		; Save return
	mov 0 3			; Save line data block ptr

	lda 0 DLB.flowControl 3	; AC0 ← flowControl
	mov 0 0 szr
	 jmp start5		; flowControl active

start2:	lda 0 .DLB.oRBD		; Compute pointer to output RBD
	add 3 0
	rrb			; Get char from ring buffer
	 jmp start1		; Empty, go clear outActive flag
	lda 0 DLB.outActive 3
	movr# 0 0 snc
	 isz DLB.outActive 3	; dlb>>DLB.outActive=true
	lda 0 .DLB.oLCB.line	; Get offset of output LCB
	add 3 0			; Make pointer for microcode
	qch			; Queue character for output
start99:
	eir
	lda 3 1 2		; Recover return
	jmp 1 3

; Here if output buffer now empty
start1:	lda 0 DLB.outActive 3
	movr# 0 0 szc
	 dsz DLB.outActive 3	; dlb>>DLB.outActive=false
	 jmp start99
	jmp start99

start5:	lda 0 DLB.flowControlAction 3	; AC0 ← action code
	movl 0 0		; shift SendXof into carry
	movl 0 0		; shift SendXon into carry
	movl 0 0 szc		; skip if flag off.
	 jmp start99		; stop bit is set.
	jmp start2		; stop bit not set.


exit:	lda 3 1 2		; Recover return
	jmp 1 3



; SendXon(dlb)

.SendXon:
	dir
	sta 3 1 2		; Save return
	mov 0 3			; Save line data block ptr
	lda 0 DLB.outActive 3
	movr# 0 0 szc
	 jmp SendXon10
	isz DLB.outActive 3	; dlb>>DLB.outActive=true
	lda 1 ..DLB.flowControlAction
	add 3 1
	lda 0 fcResetXoffSent
	andm
	lda 0 .DLB.oLCB.line	; Get offset of output LCB
	add 3 0			; Make pointer for microcode
	lda 1 cXon
	qch			; Queue character for output
	jmp start99

SendXon10:
	lda 1 ..DLB.flowControlAction
	add 3 1
	lda 0 fcSendXon
	orm
	jmp start99

..DLB.flowControlAction: DLB.flowControlAction



; DLSInputEmpty(dlb)
; Predicate for line input buffer empty

.DLSInputEmpty:
	sta 3 1 2
	mov 0 3			; DLB
	lda 0 DLB.iRBD+RBD.read 3
	lda 1 DLB.iRBD+RBD.write 3
	jmp AC0eqAC1		; read = write means empty


; DLSInputIdle(dlb)
; Predicate for input line idle (no character currently being received)

.DLSInputIdle:
	sta 3 1 2
	mov 0 3
	lda 1 DLB.line 3
	lda 0 c360		; this is now a byte value in the low byte!
	and 1 0			; first line of group
	lda 3 baseIGCB
	add 0 3			; address of IGCB
	lda 0 IGCB.idle 3
	cycle 0			; left-justify idle bit for this line
	movl# 0 0 szc
	 mkminusone 0 0 skp	; idle
	 mkzero 0 0		; not idle
	lda 3 1 2
	jmp 1 3


; DLSOutputEmpty(dlb)
; Predicate for line output buffer empty

.DLSOutputEmpty:
	sta 3 1 2
	mov 0 3			; DLB
	lda 0 DLB.oRBD+RBD.read 3
	lda 1 DLB.oRBD+RBD.write 3
	jmp AC0eqAC1		; read = write means empty


; DLSOutputFull(dlb)
; Predicate for line output buffer full

.DLSOutputFull:
	sta 3 1 2
	mov 0 3			; DLB
	lda 1 DLB.oRBD+RBD.write 3
	inc 1 1			; write+1
	lda 0 DLB.oRBD+RBD.length 3
	sne 0 1
	 mkzero 1 1		; wrap around
	lda 0 DLB.oRBD+RBD.read 3  ; read = (write+1) mod length means full

; Here to return true iff AC0 = AC1
AC0eqAC1:
	se 0 1
	 mkzero 0 0 skp		; AC0 # AC1
	 mkminusone 0 0		; AC0 = AC1
	lda 3 1 2
	jmp 1 3



; Misc utility procedures

; AndMemory(mask, adr)

.AndMemory:
	andm
	jmp 1 3


; OrMemory(mask, adr)

.OrMemory:
	orm
	jmp 1 3


; WaitForBitTimes(dlb, bitTimes)
; Dismiss for the specified number of bit times at this line's data rate.
; Note: bitTimes * ticks/bit had better not be greater than 512.

.WaitForBitTimes:
	sta 3 2 2		; use nonstandard pc place so Block won't clobber it
	mov 2 3
	mov 0 2			; dlb
	lda 0 DLB.iLCB.interval 2
	lda 2 x377
	and 0 2			; dlb>>DLB.iLCB.interval
	inc 2 2			; normalize (it was actually interval-1)
	mkzero 0 0
	mul			; [ac0, ac1] ← bitTimes*interval
	mov 3 2
	mov 1 0			; just the low result
	cycle 6			; left-justify 10-bit value to match RCLK
	mov 0 3
	rclk			; returns low 10 bits of time left-justified in ac1
	add 3 1			; ending time
	sta 1 3 2
WaitBitTimeLoop:
	jsrii lvBlock
	 0
	rclk
	lda 0 3 2
	sgt 1 0			; reached ending time?
	 jmp WaitBitTimeLoop	; no, keep waiting
	lda 3 2 2
	jmp 1 3

lvBlock: Block
x377: 377

; DLS partial reimplementation of Ring Buffer package (byte version)

; InitRingBuffer(rbd, buffer, length)
; Initialize ring buffer descriptor using specified buffer and length (words).

.InitRingBuffer:
	sta 3 1 2
	mov 0 3			; rbd
	sta 1 RBD.begin 3	; begin
	lda 0 3 2		; length
	movzl 0 0		; convert words to bytes
	sta 0 RBD.length 3
	mkzero 0 0
	sta 0 RBD.read 3	; reset read pointer
	sta 0 RBD.write 3	; reset write pointer
	jmp exit


; ResetRingBuffer(rbd)
; Delete the contents of the ring buffer

.ResetRingBuffer:
	sta 3 1 2
	mov 0 3			; rbd
	dir			; no races
	lda 0 RBD.write 3	; get write pointer
	sta 0 RBD.read 3	; update read pointer
	eir
	lda 3 1 2
	jmp 1 3


; ReadRingBuffer(rbd) = next char from ring buffer, or -1 if empty

.ReadRingBuffer:
	rrb
	 mkminusone 1 1		; empty
	mov 1 0
	jmp 1 3


; WriteRingBuffer(rbd, char)
; Write Char into ring buffer
; Return true if succeeded, false if failed (buffer full)
; Note: this routine depends on the left half of Char being zero

.WriteRingBuffer:
	wrb
	 mkzero 0 0 skp		; full
	 mkminusone 0 0		; not full
	jmp 1 3


	.end