{ File: [Iris]<WMicro>DLion>RS232CGet.asm
Modification History:
JAC : 12-Jun-85 15:49:19 Change AsyncTimeout to AsyncTO to resolve the conflict with multiport
Dennis DEG : 6-Nov-84 17:14:25: Pick up Michael MAT 's fix for check of LastBufferFlag of 21-Aug-84 17:12:40
Dennis DEG : 1-Sep-84 17:09:06 Added copyright notice
Michael MAT : 21-Dec-83 14:39:46: Add Async Flow Control Handling
Dennis DEG : 9-Oct-83 16:32:55: Cleanup code.
AEF AEF : 13-Jun-83 16:05:12: Add CTS test at start of task
CRF CRF : 10-Nov-82 17:58:24: Fixed FifoOverflow handling.
Jim JXF : September 17, 1982 9:01 AM: allow for async blocks to be bigger
than the client's buffer.
Jim JXF : September 9, 1982 8:39 AM: turn off Success bit when error code
is returned. Change handling of Abort.
Jim JXF : July 30, 1982 1:30 PM: new IO Page format. Add CRC error as 20H
and FramingError as 8.
Jim JXF : March 14, 1982 2:23 PM: created file}
{ Copyright (C) 1982, 1983 by Xerox Corporation. All rights reserved.}
Get "SysDefs"
Get "CommonDefs"
Get "RS232CDefs"
IMP ByteToWord ;From CPSubs
IMP CharLengthMask ;From RS232CInterrupts
IMP CharLengthMask1 ;From RS232CInterrupts
IMP CharactersLeftInClientBuffer ;From RS232CInterrupts
IMP CharactersUntilBoundary ;From RS232CInterrupts
IMP CheckCPDmaComplete ;From CPSubs
IMP CurrentCharLengthMask ;From RS232CMisc
IMP CurrentFrameToFillPtr ;From RS232CInterrupts
IMP CurrentFrameToSendPtr ;From RS232CInterrupts
IMP DmaActive ;From CPSubs
IMP DoNakedNotify ;From CPSubs
IMP FCState ;From RS232CInterrupts
IMP FifoStateSwitch ;From RS232CInterrupts
IMP FirstFrameToFillPtr ;From RS232CInterrupts
IMP FirstMaxFrameBoundary ;From RS232CInterrupts
IMP FirstRxCharPtr ;From RS232CInterrupts
IMP FlowControlFlag ;From RS232CInterrupts
IMP LastBufferFlag ;From RS232CInterrupts
IMP LastNewFrameAddressHigh ;From RS232CInterrupts
IMP LastNewFrameAddressLow ;From RS232CInterrupts
IMP MaxFrameSize ;From RS232CInterrupts
IMP MainLoop ;From BookKeepingTask
IMP OutputXon
IMP PortBusyFlag ;From CPSubs
IMP ReadCPBuffer ;From CPSubs
IMP RS232CGetFlag ;From BookKeepingTask
IMP RS232CMode ;From RS232CMisc
IMP RS232CTaskWakeMask ;From RS232CMisc
IMP RxBisyncStateSwitch ;From Common
IMP RxBisyncInitial ;From BisyncInput
IMP RxFIFO ;From Buffer
IMP RxFifoEnd ;From RS232CInterrupts
IMP RxFifoState ;From RS232CInterrupts
IMP RxMaxFrameSize ;From BisyncInterrupts
IMP RxWR3 ;From RS232CMisc
IMP SetLastBufferFlag ;From RS232CInterrupts
IMP SdlcFrameFlag ;From RS232CInterrupts
IMP TerminateFrame ;From RS232CInterrupts
IMP TerminateBisyncFrame ;From BisyncInput
IMP TestAsyncTimer ;From RS232CSubs
IMP TxWR1 ;From RS232CMisc
IMP WriteCPBuffer ;From CPSubs
IMP ZeroCommand ;From Common
EXP AbortGetFlag
EXP AsyncGetLoop
EXP BisyncGetLoop
EXP FloppyActiveSwitch4
EXP GetByteCount
EXP GetFifoOverflow
EXP GetModeSwitch
EXP GetTaskResumeAddress
EXP GetTaskStartAddress
EXP GetTaskQuiet
EXP PrevFrameToSendPtr ;***DEBUGGING
EXP SdlcGetLoop
EXP StartGetTask
EXP StatusChangeFlag
EXP TestStatusChange
;
RS232CGetTask:
DB opJMP
GetTaskStartAddress:
DW GetTaskQuiet
; JMP GetTaskQuiet ; Set by Misc Off command
; JMP NotifyStatusChange ; Set in code below
; JMP TestStatusChange ; Set by Misc SetParameters
{Check StatusChangeFlag. This gets set by the External Interrupt routine if we detect CTS (Clear to Send) or DCD (Data Carrier Detect). If this is set, we do a Naked Notify.}
TestStatusChange:
LDA StatusChangeFlag
ORA A
JZ RS232CTaskStart
NotifyStatusChange:
LDA PortBusyFlag
ORA A
JZ SendStatusNotify
LXI H, NotifyStatusChange
SHLD GetTaskStartAddress
JMP RS232CTaskStart
SendStatusNotify:
XRA A
STA StatusChangeFlag
LHLD RS232CTaskWakeMask
CALL DoNakedNotify
LXI H, TestStatusChange
SHLD GetTaskStartAddress
RS232CTaskStart:
DB opJMP
GetTaskResumeAddress:
DW GetTaskQuiet
; JMP WaitForIocbTransfer
; JMP StartGetTask ;Set by Misc SetParameters Command
; JMP WaitForIocb
; JMP GetLoop
; JMP SendFrame
; JMP WaitForDataTransfer
; JMP ReturnGetIocb
;
StatusChangeFlag:
DB 0
RS232CGetCSB:
GetIOCBAddressPointerLo:
DS 2 ;14033
GetIOCBAddressPointerHi:
DS 2 ;14034
{The Iocb is arranged so that we can write the Iocb, the CSB, and the NakedNotify in one transaction. Note that the IOCB is in reverse byte form because it is read and written with the DMA.}
RS232CGetIocbSize EQU 10
{This is the Get IOCB that is transferred to and from the CP.}
GetIocbBuffer:
GetBufferAddressLo:
DB 0
GetBufferAddressMid:
DB 0 ; blockPointer: LONG POINTER
GetBufferAddressHi:
DB 0
DB 0 ;Not used
GetByteCount:
DB 0 ; byteCount: CARDINAL
GetByteCountHi:
DB 0
GetDataByteCount:
DB 0 ; returnedByteCount: CARDINAL
GetDataByteCountHi:
DB 0
GetTransferStatus:
DB 0 ; transferStatus: iopTransferStatus
GetTransferStatusHi:
DB 0
GetIocbPcb:
{ Port Control Block to Read and Write the Get IOCB.}
GetPcbAddressLo:
DW 0
GetPcbAddressHi:
DW 0 ; = 0 (0 MDS)
DW RS232CGetIocbSize ; Size in bytes
DW GetIocbBuffer
RS232CGetCPPort:
{ Port Control Block to read the RS232C CSB from the IOPage }
DW RS232CGetCSBLoc ; CP Buffer Pointer Low
DW CPIOPageHi ; CP Buffer Pointer Hi
DW RS232CGetCSBSize ; Size in bytes
DW RS232CGetCSB ; Pointer to Buffer in Common
ResetGetFlag:
{ Port Control Block to reset the Get flag in the IOPage }
DW RS232CGetFlagLoc ; CP Buffer Pointer Low
DW CPIOPageHi ; CP Buffer Pointer Hi
DW 2 ; Size in bytes
DW ZeroCommand
PrevFrameToSendPtr:
DW 0 ;***FOR DEBUGGING
;
{The interrupt routine puts characters into the Fifo, and the Get routine removes one frame at a time. Each time a frame is ready, the Fifo routine notifies the Get routine by posting a completion code in the frame header. The interrupt routine fills a frame and sets the FrameCompletionFlag # 0 when a frame is ready to send. The interrupt routine stores the address of the last character stored + 1 in FrameLastChar.
We never start a new frame unless there is room in the Fifo for the maximum frame size + the size of the frame header. RxMaxFrameSize contains the maximum frame size. We get an overflow when there is no room in the Fifo for a new frame.
The first frame begins at RxFifo. The next frame begins at the next address following the first frame. If the following calculation is negative, we need to start a new frame at the beginning of the RxFifo: LastNewFrameAddress - NextFrameToFillPtr.
This is what a frame looks like:}
FrameKey:
EQU 0 ;# FrameValid => frame has been stored over
FrameReadyFlag:
EQU 1 ;#0 => frame is ready to send
FrameLastCharLow:
EQU 2
FrameLastCharHigh:
EQU 3 ;stored by interrupt routine
FrameCPAddrLow:
EQU 4 ;low byte of CP address
FrameCPAddrMid:
EQU 5 ;middle byte of CP address
FrameCPAddrHigh:
EQU 6 ;high byte of CP address
FrameSizeLow:
EQU 7 ;number of data bytes in frame
FrameSizeHigh:
EQU 8
FrameCommand:
EQU 9 ;WriteBlock command for CP micrrocode
FrameData:
EQU 10 ;First character of data
FrameValid EQU 7EH
;
StartGetTask:
FetchGetIocb:
LDA RS232CGetFlag
ORA A
JNZ ReadGetCSB
GetTaskQuiet:
{Nothing to do yet because there is no Iocb Ready. Turn off the AbortGetFlag in case Misc is waiting for AbortGet.}
XRA A
STA AbortGetFlag
JMP GetTaskYield
{The FullFlag is on to indicate that an IOCB is ready. See if the CP port is free.}
ReadGetCSB:
LDA PortBusyFlag
ORA A
JNZ GetTaskYield
{Read the CSB to assure that we have the correct IOCB data.}
LXI H,RS232CGetCPPort
CALL ReadCPBuffer
{Copy IOCB address from the CSB into the Port Control Block. We know that the IOCB is in MDS zero, so don't bother to copy the high address. Then copy the IOCB from the CP into IOP memory.}
LHLD GetIocbAddressPointerLo
SHLD GetPcbAddressLo
LXI H,GetIocbPcb
CALL ReadCPBuffer ; Copy IOCB from Main Memory
{Initialize the address of the data buffer in CP memory. This will be incremented if the data is transferred back to the CP in blocks.}
LHLD GetBufferAddressLo
MOV A,L
STA BufferAddressLo
MOV A,H
STA BufferAddressMid
LDA GetBufferAddressHi
STA BufferAddressHi
LXI H,0
SHLD GetDataByteCount ;Zero bytes returned field
LXI H,XferSuccess ;Assume Get will be successful
SHLD GetTransferStatus
{Initialize the size of the CPBuffer so that the interupt routine will know when to end a frame. For Async Bisync mode, the IOCB size can be greater than 4K bytes, so we must choose a smaller number and send the data back to the CP in smaller blocks.}
LHLD GetByteCount
MOV A,L
STA RxClientBufferSizeLow
MOV A,H
STA RxClientBufferSizeHigh
LDA RS232CMode
CPI CPSDLCMode
JZ SetMaxFrameSize ;if SDLC
LXI H,600 ;Set max block size if Async or Bisync
SetMaxFrameSize:
SHLD RxMaxFrameSize
{Enable Rx if not yet enabled. We must not enable until we have received the first IOCB so we will know how big the CP buffer is. The interrupt routine needs to know when it fills the IOCB.}
LDA RxWR3
ANI RxEnable
JNZ GetLoop
;
{Start here when the first IOCB is ready and we have not yet done our first Get. We also come through here if we get an invalid frame that wipes out a frame, and we must re-initialize the Fifo. This can happen if we get a block bigger than the maximum size.}
LDA CurrentCharLengthMask
STA CharLengthMask
STA CharLengthMask1
{Disable interrupts here. If we are re-initializing because of a Fifo overflow, we don't want the interrupt routine to change anything until we have re-initialized.}
DI
{HL = max frame size. Initialize CharactersUntilBoundary to the size of the frame. We assume that our first boundary will be when we fill the first frameor client buffer. The FifoState starts in the Open sate.}
SHLD CharactersUntilBoundary
LHLD GetByteCount
SHLD CharactersLeftInClientBuffer
MVI A,opJNZ ;Set FifoState to Open
STA FifoStateSwitch
STA RxFifoState
{Initialize the Fifo address. Note that CurrentFrameToFillPtr points to FrameCompletionCode, and other pointers point to FrameValid.}
LXI H,RxFIFO ;HL points to RxFifo
SHLD FirstFrameToFillPtr
SHLD CurrentFrameToSendPtr
SHLD CurrentFrameToSendPtr1
SHLD CurrentFrameToSendPtr2
XRA A
STA StatusChangeFlag
STA SdlcFrameFlag
MVI M,FrameValid
INX H ;HL points to FrameCompletionFlag
SHLD CurrentFrameToFillPtr
MOV M,A ;Turn off FrameCompletionFlag
LXI D,FrameData-1
DAD D ;HL points to first data char of first frame
SHLD FirstRxCharPtr
{Set NextRxCharPtr. Note that BC are reserved for this. BC are not used anywhere else in Domino.}
MOV B,H
MOV C,L ;BC ← NextRxCharPtr
LXI H,RxBisyncInitial
SHLD RxBisyncStateSwitch
EI
LXI H,ReturnGetIocb
SHLD FrameSentSwitch
LXI H,YieldForTransfer
SHLD WaitForTransferSwitch
{Calculate the minimum frame size: RxMaxFrameSize + FrameData. The interrupt routine needs to know if there is room for another frame in the Fifo.}
LHLD RxMaxFrameSize
LXI D,FrameData
DAD D ;HL ← MaxFrameSize
SHLD MaxFrameSize
XCHG ;DE ← MaxFrameSize
LHLD FirstFrameToFillPtr
DAD D
SHLD FirstMaxFrameBoundary
{Calculate the last new frame address in the RxFifo: FifoBoundary - MaxFrameSize. When a frame ends, the interrupt routine needs to know if we can start another frame following the previous frame, or if we should start the next frame at the beginning of the RxFifo. If the following calculation is negative, we need to start at the beginning of the RxFifo: LastNewFrameAddress - NextNewFrameAddress.}
LHLD RxFifoEnd
MOV A,L
SUB E ;Subtract MaxFrameSize
STA LastNewFrameAddressLow
STA LastNewFrameAddressLow1
MOV A,H
SBB D
STA LastNewFrameAddressHigh
STA LastNewFrameAddressHigh1
{Turn on the receiver.}
LXI H,8000H+RxCont
LDA RxWR3
ORI RxEnable
STA RxWR3
DI
MVI M,PointToWR3+ResetRxCRCChecker
MOV M,A
EI
{Now that WR3 is set up to enable Rx, we will start getting interrupts from the SIO chip, and we can start receiving data.}
GetLoop:
DB opJMP
GetModeSwitch:
DW AsyncGetLoop
; JMP AsyncGetLoop
; JMP SdlcGetLoop
; JMP BisyncGetLoop
;
BisyncGetLoop:
{Test to see if the interrupt routine has filled a frame.}
DB opLHLD ;H ← FrameCompletionFlag, L ← FrameValid
CurrentFrameToSendPtr2:
DW 0
MOV A,H ;A ← FrameCompletionFlag
ORA A
JZ TestBisyncAbort
BisyncFrameCompletion:
{Come here when the interrupt routine has terminated a frame.
H = FrameCompletionFlag, L = FrameValid
A indicates the reason for the termination:
bit 0 (mask 80) End of Frame
bit 1 (mask 40) CRC Error or Framing Error
bit 2 (mask 20) Overrun
bit 3 (mask 10) Parity Error
bit 4 (mask 8) Invalid character sequence
bit 5 (mask 4) End Of Block
bit 6 (mask 2) Fifo Overflow
bit 7 (mask 1) Abort
}
MOV A,L
CPI FrameValid
JNZ InvalidFrame
MOV A,H
CPI 8
JNZ FrameCompletion
MVI L,InvalidCharacter
JMP StoreErrorStatus
TestBisyncAbort:
LDA AbortGetFlag
ORA A
JNZ AbortFlagSet
LXI H,GetLoop
JMP SaveGetResumeAddressAndYield
AbortFlagSet:
MVI E,1 ;Set CompletionCode for Aborted
{Set up the stack so we can call the interrupt routine. We need 3 words plus a return address on the stack. CompletionCode is in E.}
LXI H,BisyncGetLoop
PUSH H ;push return address
PUSH PSW
PUSH D
DI
CALL TerminateBisyncFrame ;push one more word and JMP
;
SdlcGetLoop:
{Test to see if the interrupt routine has filled a frame.}
DB opLHLD ;H ← FrameCompletionFlag, L ← FrameValid
CurrentFrameToSendPtr1:
DW 0
MOV A,H ;A ← FrameCompletionFlag
ORA A
JZ TestAbort
SdlcFrameCompletion:
{Come here when the interrupt routine has terminated a frame.
H = FrameCompletionFlag, L = FrameValid
A indicates the reason for the termination:
bit 0 (mask 80) End of Frame
bit 1 (mask 40) CRC Error or Framing Error
bit 2 (mask 20) Overrun
bit 3 (mask 10) Parity Error
bit 4 (mask 8) Not Used
bit 5 (mask 4) Not Used
bit 6 (mask 2) Fifo Overflow
bit 7 (mask 1) Abort
}
MOV A,L
CPI FrameValid
MOV A,H
JZ FrameCompletion
InvalidFrame:
{Come here if the frame has been stored over. In SDLC mode, there is no time to check to see if we are storing over a frame to send. We must trust that we will not get a frame that is larger than the client's IOCB. If a frame gets stored, over, return an error code and reinitialize the Fifo.}
{Turn off the receiver.}
LXI H,8000H+RxCont
LDA RxWR3
ANI 0FFH-RxEnable
DI
MVI M,PointToWR3
MOV M,A
EI
STA RxWR3
MVI A,DataLost
STA GetTransferStatusHi
JMP ReturnGetIocb
;
AsyncGetLoop:
{When running in Async mode, we must test for timeout. If we get a timeout, we end the frame and ship this one back to the CP.}
LDA LastBufferFlag
ORA A
JNZ TestCFTS
CALL TestAsyncTimer
MVI E,80H ;Set CompletionCode for Timeout
JZ AsyncTO
TestCFTS:
{Test to see if the interrupt routine has filled a frame.}
LHLD CurrentFrameToSendPtr
INX H ;Point to FrameCompletionCode
MOV A,M
ORA A
JNZ FrameCompletion
TestAbort:
DB opORI ;A ← AbortGetFlag
AbortGetFlag:
DB 0 ;Check for AbortInput Command
JNZ AbortGet ;Jump if abort
LXI H,GetLoop
JMP SaveGetResumeAddressAndYield
{We will return to GetLoop when it is our turn to run again.}
FrameCompletion:
{Come here when the interrupt routine has terminated a frame.
A = FrameCompletionFlag
A indicates the reason for the termination:
bit 0 (mask 80) End of Frame
bit 1 (mask 40) CRC Error or Framing Error
bit 2 (mask 20) Overrun
bit 3 (mask 10) Parity Error
bit 4 (mask 8) Not Used
bit 5 (mask 4) End Of Block
bit 6 (mask 2) Fifo Overflow
bit 7 (mask 1) Abort
}
CPI 80H ;Test for End of Frame
JZ SendFrame
CPI 4
JZ EndOfBlock
CPI 1
JZ AbortDetected
MOV H,A
ANI FifoOverflowed
JNZ GetFifoOverflow
MOV A,H
ANI 40H
JNZ CRCErrorDetected
MOV A,H
ANI 10H
JNZ ParityErrorDetected
OverrunErrorDetected:
MVI L,DeviceError
JMP StoreErrorStatus
AbortDetected:
{Store Aborted in TransferStatus, 0 in TransferStatusHi.}
MVI L,Aborted
JMP StoreErrorStatus
CRCErrorDetected:
MVI L,CRCError
JMP StoreErrorStatus
FramingErrorDetected:
MVI L,FramingError
JMP StoreErrorStatus
ParityErrorDetected:
MVI L,ParityError
StoreErrorStatus:
MVI H,0 ;Zero success bit
SHLD GetTransferStatus
JMP SendFrame
AbortGet:
{Come here if we get an Abort command from Misc. Abort this block. Store Aborted in TransferStatus, 0 in TransferStatusHi.}
MVI E,1 ;Set CompletionCode for Aborted
LXI H,GetLoop ;Set return address
JMP PushReturnFromTerminateFrame
AsyncTO:
{When we get a timeout in Async mode, we end the current frame. Set up the stack so we can call the interrupt routine. We need 3 words plus a return address on the stack. CompletionCode is in E.}
LXI H,SendFrame
PushReturnFromTerminateFrame:
PUSH H ;push return address
PUSH PSW
PUSH D
DI
CALL TerminateFrame ;push one more word and JMP
EndOfBlock:
{Come here if this is the end of a block in Async or Bisync mode. Send this frame back to the CP. Return to BlockSent when the transfer is complete.}
LXI H,BlockSent
SHLD FrameSentSwitch
LXI H,WaitForTransfer
SHLD WaitForTransferSwitch
JMP SendFrame
BlockSent:
LXI H,YieldForTransfer
SHLD WaitForTransferSwitch
LXI H,ReturnGetIocb
SHLD FrameSentSwitch
{Increment the CP buffer address.}
LDA BufferAddressLo
MOV E,A
LDA BufferAddressMid
MOV D,A
LHLD CurrentFrameDataBytes
CALL ByteToWord
DAD D
MOV A,L
STA BufferAddressLo
MOV A,H
STA BufferAddressMid
LDA BufferAddressHi
ACI 0
STA BufferAddressHi
{Update the number of characters left in the IOCB so that the SendFrame routine can test to be sure we don't send more characters than will fit in the IOCB.}
LHLD CurrentFrameDataBytes
LDA RxClientBufferSizeLow
SUB L
STA RxClientBufferSizeLow
LDA RxClientBufferSizeHigh
SBB H
STA RxClientBufferSizeHigh
JMP GetLoop
GetFifoOverflow:
{Come here if the RxFifo has overflowed. Either we could not keep up with the input, or the Fifo is not big enough. The interrupt routine has stored this frame over another frame. This means it threw away a frame. Return this frame to the CP with DataLost error code
}
MVI A,DataLost
STA GetTransferStatusHi
SendFrame:
{Come here when we have a frame ready to send to the CP. The interrupt routine has stored the pointer to the last character stored into the FrameLastChar field of the frame. Prepare to send this frame to the CP. We will send the WriteBlock Command and the data in one transaction.}
LDA PortBusyFlag
ORA A
JZ StartFrameTransfer
LXI H,SendFrame
JMP SaveGetResumeAddressAndYield
StartFrameTransfer:
LHLD CurrentFrameToSendPtr
INX H
INX H ;HL points to FrameLastCharLow
MOV A,M ;A ← LastCharLow
INX H ;HL points to FrameLastCharHigh
SUB L ;Addr(LastChar) - Addr(FrameLastCharHigh)
MOV E,A
MOV A,M ;A ← LastCharHigh
SBB H
MOV D,A
{DE contains Addr(last char stored+1) - Addr(FrameLastCharHigh) = number of data bytes + 7}
MOV A,E
SUI 7
MOV E,A
MOV A,D
SBI 0
MOV D,A
INX H ;HL points to FrameCPAddrLow
{Now DE contains the number of data characters in the frame. Be sure the block will fit in the client's buffer. Calculate RxClientBufferSize - DE. If this is negative, the block is too big, and we may have stored over another frame in the Fifo.}
DB opMVIA ;A ← RxClientBufferSizeLow
RxClientBufferSizeLow:
DB 0
SUB E
DB opMVIA ;A ← RxClientBufferSizeHigh
RxClientBufferSizeHigh:
DB 0
SBB D
JM InvalidFrame
{DE contains the number of data characters in the frame. HL points to the first character that we will send to the CP. Send the address in HL to the DMA.}
MOV A,L
OUT DmaCh1Addr ;Send low address
MOV A,H
OUT DmaCh1Addr ;Send high address
{Make the number of data bytes even. We must send an even number to the CP.}
XCHG ;DE points to FrameCPAddrLow
;HL ← count
SHLD CurrentFrameDataBytes
INX H
MOV A,L
ANI 0FEH ;Make it even
MOV L,A ;HL ← even data byte count
ORA H ;Test for zero count
JZ UpdateCurrentFrameToSend
PUSH H ;Stack ← CurrentFrameEvenDataBytes
{DE points to FrameCPAddrLow. HL contains the even number of data bytes. Calculate the FrameByteCount, the next field of the CPTransferControlBlock. This is the total number of bytes we send to the CP - 1: (data byte count) + (WriteBlock Command byte count) - 1 = (data byte count) + 5 = HL + 5. FrameByteCountHigh must contain the DMA transfer command in the high two bits. For Read, we want DmaFuncRead = 80H in the high byte.}
PUSH D
LXI D,8005H ;BC ← 5 + DmaFuncRead
DAD D ;HL ← HL + 5 = data byte count + 5
POP D
{Send the count and the command to the DMA.}
MOV A,L
OUT DmaCh1Count
MOV A,H
OUT DmaCh1Count
{Store the CPAddress in FrameCPAddrLow, Mid, High}
XCHG ;HL points to FrameCPAddrLow
DB opMVIM ;M ← BufferAddressLo
BufferAddressLo:
DB 0
INX H ;HL points to FrameCPAddrMid
DB opMVIM ;M ← BufferAddressMid
BufferAddressMid:
DB 0
INX H ;HL points to FrameCPAddrHigh
DB opMVIM ;M ← BufferAddressHi
BufferAddressHi:
DB 0
INX H ;HL points to FrameSizeLow
{Store the number of data bytes in FrameSizeLow, High. }
POP D ;DE ← CurrentFrameEvenDataBytes
MOV M,E ;Store FrameSizeLow
INX H ;HL points to FrameSizeHigh
MOV M,D ;Store FrameSizeHigh
INX H ;HL points to FrameCommand
MVI M,CPWriteCmd ;Store WriteBlockCommand
{One final check to be sure the interrupt routine has not stored into our frame header. This can happen if we get a block that is bigger than the client's Iocb. We don't have time to check for this in the interrupt routine.}
LHLD CurrentFrameToSendPtr
MOV A,M
CPI FrameValid
JNZ InvalidFrame
{Now we are ready to start the DMA for the CP channel. When we send the command to the DMA controller, the low 4 bits of the command specify which channels to start. We must start the CP channel as well as any other channels that are currently busy.
The problem is to know what channels are currently busy so we can send the proper command to the controller. DmaActive is the command we last sent to the DMA to start one or more channels. The low 4 bits of this command indicate which channels were last started. Some of these channels may have completed since then.
In order to find out what channels have completed, we must read the Status Register. The low 4 bits of this register indicate what channels have completed. However, we cannot read this register and use this information to restart channels that have not completed, because a channel could complete in this interval. To leave a channel enabled is like pointing a loaded gun at our memory. So what me must do is to stop all channels except for the floppy channel before we read the Status Register. We must not stop the floppy channel if it is busy because it cannot afford the time.
Set up A with the ModeSetRegister contents. If the floppy was busy, we will set the floppy active and turn all other channels off. If the floppy was not busy, we will turn off all channels.}
LXI H,DmaActive
{If the floppy and RS232C are running at the same time, we must disable interrupts when DmaActive is being updated. The following op code will be a NOP whenever the floppy is NOT active. It will be DI whenever the floppy IS active.}
FloppyActiveSwitch4:
NOP
MOV A,M ;A ← DmaActive
ANI DmaCh0Mask ;A ← 1 if floppy was busy
OUT DmaMode ;Point to DmaMode register
IN DmaStatus ;Disable channels
CMA ;A ← inverted TerminalCount Flags
{Now A contains zero in bit positions of channels that have completed and ones in all other bit positions. We AND this with DmaActive. This gives us a command to restart all channels that need to be restarted. We then OR in the bit for the CP channel, send the command to the DMA, and save DmaActive.}
ANA M
ORI DmaCh1Mask ;Start CP Channel
OUT DmaMode ;Send command to DMA
MOV M,A ;Save DmaActive
EI ;If disabled by FloppyActiveSwitch4
{The DMA is now programmed and enabled.}
MVI A,CPEnable+CPDmaMode
OUT CPControl ;Set DmaMode (clear IOPWait, SwTAddr, DmaIn)
STA PortBusyFlag
{If we are sending a block of data for Bisync mode, wait for the transfer to complete. Otherwise the frames will catch up with us and we will get a Fifo overflow.}
DB opJMP
WaitForTransferSwitch:
DW YieldForTransfer
; JMP YieldForTransfer
; JMP WaitForTransfer
WaitForTransfer:
CALL CheckCPDmaComplete
JNZ UpdateCurrentFrameToSend
JMP WaitForTransfer
YieldForTransfer:
LXI H,WaitForDataTransfer
JMP SaveGetResumeAddressAndYield
WaitForDataTransfer:
CALL CheckCPDmaComplete
JZ GetTaskYield
;
UpdateCurrentFrameToSend:
{Now that the transfer has completed, it is safe to update the CurrentFrameToSendPtr to point to the next frame in the Fifo.. If we update before the completion, it is possible that a frame being filled will run into the frame being sent. The interrupt routine is already filling the next frame. Set HL to point to the next character after the current frame: CurrentFrameToSendPtr + CurrentFrameDataBytes + FrameData}
LHLD CurrentFrameDataBytes
XCHG ;DE ← CurrentFrameDataBytes
LHLD GetDataByteCount
DAD D ;HL ← total bytes this IOCB
SHLD GetDataByteCount
LHLD CurrentFrameToSendPtr
SHLD PrevFrameToSendPtr ;*** FOR DEBUGGING
DB opLXID ;DE ← CurrentFrameDataBytes
CurrentFrameDataBytes:
DW 0
DAD D ;HL + CurrentFrameDataBytes
LXI D,FrameData
DAD D ;HL ← address of next character
{If the following calculation is negative, we need to start a new frame at the beginning of the RxFifo: LastNewFrameAddress - NextFrameToSendPtr}
DB opMVIA ;A ← LastNewFrameAddressLow
LastNewFrameAddressLow1:
DB 0
SUB L
DB opMVIA ;A ← LastNewFrameAddressHigh
LastNewFrameAddressHigh1:
DB 0
SBB H
JP UpdateFrameToSend
{Come here if the next frame starts at the beginning of the RxFifo. The Fifo State changes from Closed to Open. Disable interrupts for as short a time as possible.}
LHLD FirstFrameToFillPtr
MVI A,opJNZ ;Set FifoState to Open
DI
STA FifoStateSwitch
STA RxFifoState
UpdateFrameToSend:
SHLD CurrentFrameToSendPtr
EI
LDA FlowControlFlag ;IF ~FlowControl
ORA A
JNZ TestLastBuffer
SHLD CurrentFrameToSendPtr1 ;Then save CFTS↑
SHLD CurrentFrameToSendPtr2
JMP FrameSentSwitch-1
TestLastBuffer:
DI
LHLD CurrentFrameToFillPtr ;Else {SetLasBufferFlag
CALL SetLastBufferFlag
EI
LDA LastBufferFlag
ORA A
JNZ FrameSentSwitch-1 ; IF ~LastBuffer & (SendingXoff | XoffSent)
TestXoffSent:
LDA FCState
ANI SendingXoff+XoffSent
JZ FrameSentSwitch-1
DI
CALL OutputXon ; THEN Send Xon Char}
EI
{If we have sent a block of data for Bisync mode, return to Bisync processing.}
DB opJMP
FrameSentSwitch:
DW ReturnGetIocb
; JMP ReturnGetIocb
; JMP BlockSent
;
ReturnGetIocb:
{ Copy the IOCB back to the CP. Reset the Full flag so we will wait for the next IOCB. Notify the CP that we have completed this IOCB.}
LDA PortBusyFlag
ORA A
JZ SendGetIocb
LXI H,ReturnGetIocb
JMP SaveGetResumeAddressAndYield
SendGetIocb:
STA AbortGetFlag ;Reset AbortGetFlag
LXI H,GetIocbPcb
CALL WriteCPBuffer ;Send IOCB back to CP
LXI H,ResetGetFlag
CALL WriteCPBuffer
LHLD RS232CTaskWakeMask
CALL DoNakedNotify
LXI H,FetchGetIocb
SaveGetResumeAddressAndYield:
SHLD GetTaskResumeAddress
GetTaskYield:
{Continue to next task. RS232CGet is the last task in Domino.cfg, so we jump back to the top of round-robin task management.}
JMP MainLoop
END