{ File: [Iris]<WMicro>DLion>RS232CGet.asm Modification History: Dennis Grundler: 6-Nov-84 17:14:25: Pick up Michael Thatcher's fix for check of LastBufferFlag of 21-Aug-84 17:12:40 Dennis Grundler: 1-Sep-84 17:09:06 Added copyright notice Michael Thatcher: 21-Dec-83 14:39:46: Add Async Flow Control Handling Dennis Grundler: 9-Oct-83 16:32:55: Cleanup code. Amy Fasnacht : 13-Jun-83 16:05:12: Add CTS test at start of task Chuck Fay : 10-Nov-82 17:58:24: Fixed FifoOverflow handling. Jim Frandeen : September 17, 1982 9:01 AM: allow for async blocks to be bigger than the client's buffer. Jim Frandeen : September 9, 1982 8:39 AM: turn off Success bit when error code is returned. Change handling of Abort. Jim Frandeen : July 30, 1982 1:30 PM: new IO Page format. Add CRC error as 20H and FramingError as 8. Jim Frandeen : 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 AsyncTimeout 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 AsyncTimeout: {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