{  File: [Idun]<WDLion>DmaSubs.asm
Modification History:
Last change by Jim Frandeen May 21, 1982  9:01 AM: Change DmaControlBlock Format and Port Control Block format to be compatible with each other. Save mode bits in DmaActive.
Changed StartDmaChannel so DmaChannel cannot be destroyed by
interrupting subroutine (April 3, 1981  7:04 AM).
Changed di/ei to use subroutines (March 25, 1981  9:34 PM)
Leave Floppy Channel enabled while StartDmaChannel enables another
channel in an effort to reduce the LostData errors
(January 26, 1981  11:52 AM)
protected StartDmaChannel, ClearDmaChannel, ReadDmaCompletion from
interference by the Floppy Interrupt (January 25, 1981  4:00 PM)
Shortened critical section in StartDmaChannel (January 20, 1981  1:56 PM)
Removed DmaCompletion, changed ClearDmaChannel (December 18, 1980  2:09 PM)
Shortened critical section in StartDmaChannel (October 31, 1980  12:11 PM)
Created by Roy Ogus (June 17, 1980  1:48 PM)
}

;  DEFINITION FILES:

	get "SysDefs"		;system defs
	get "CommonDefs"	;Common defs


;  IMPORTS/EXPORTS:

	IMP	ErrorReport

	EXP	ClearDmaChannel
	EXP	DmaActive
	EXP	FloppyActiveSwitch1
	EXP	FloppyActiveSwitch2
	EXP	FloppyActiveSwitch3
	EXP	ReadDmaCompletion
	EXP	StartCPDmaChannel
	EXP	StartDmaChannel
	EXP	StartFloppyChannel

{  NOTES:

These are the subroutines which handle the Dma Controller.  Clients using the Dma Controller should use them to set up, start, or disable a particular channel.

DmaControlBlock Format:
	byte 0: Byte count low,
	byte 1: Byte count high,
	byte 2: Memory Address Low,
	byte 3: Memory Address high,

NOTE: the DmaControlBlock is the same as the last two words of the PortControlBlock.

The following are the subroutines available to clients:
  -  StartDmaChannel: 
	[H,L: POINTER to DmaControlBlock,
	A: Function/DmaChannelMask]

  -  Function/DmaChannelMask:
	Function (bits 0,1) :  0 = Verify, 1 = write, 2 = read, 3 = undefined
	DmaChannelMask (bits 4..7): EN3, EN2, EN1, EN0

Set up the address, count, and operation for the particular Dma channels. Other channels are not affected.  Channels which have completed are removed from DmaActive.  "DmaActive" is updated in memory. Returns StartError =0 for no error, StartError#0 for error in channel mask.

  -  ReadDmaCompletion [A: DmaChannelMask] RETURNS [A: ~DmaActive AND DmaChannelMask]

Read Dma Status, and update DmaActive.Return ~DmaActive AND DmaChannelMask.

  -  ClearDmaChannel [A: DmaChannelMask]

Determine if any channels have completed, and update DmaActive.  Clear the Active bits in DmaActive as indicated by DmaChannelMask.  Re-enable the DmaComtroller with the new Active bits.

Questions:
  -  what if channel is active and a StartChannel is received?
}



; Internal definitions:
ChannelMask	equ	0FH	;Mask of Channel field in Function/ChannelMask byte
nChannelMask	equ	0FFH-ChannelMask	;Complement of ChannelMask
FunctionMask	equ	0C0H	;Mask of Function field in Function/ChannelMask byte
DisableDma	equ	0	;Dma Mode value to disable all channels

;
;  PUBLIC Dma Subroutines.


{  Subroutine:  StartCPDmaChannel [H,L:  POINTER to DmaControlBlock].
Set up the address, count, Dma function of the CP channel.
Enable the Dma Channel.  Note: TCS and EW are set in Mode automatically. 

Assumption:  Byte count is only 14 bits, i.e. high 2 bits are zero. NO DMA CHANNEL SHALL BE RESTARTED WITHOUT USING ReadDmaCompletion OR ClearDmaChannel TO ENSURE THE CHANNEL'S TERMINAL COUNT BIT IS OFF WHEN StartDmaChannel ACCESSES THE DMA CHIP.  If the TC bit is on, the channel will not be restarted.
  On entry:
    A = function: DmaRead or DmaWrite
    HL  =  pointer to Dma Control Block (format described above).
	byte 0: Byte count low,
	byte 1: Byte count high,
	byte 2: Memory Address Low,
	byte 3: Memory Address high,
}

StartCPDmaChannel:
	MOV	E,M		;E ← low byte count
	INX	H		;Point to High count
	MOV	D,M		;D ← High byte count
	DCX	D		;Decrement for Dma controller
	ORA	D		;OR in Dma function
	MOV	D,A
	MOV	A,E
	OUT	DmaCh1Count	;Send low count
	MOV	A,D
	OUT	DmaCh1Count	;Send high count
	INX	H		;Point to Low address
	MOV	A,M		;E ← Memory Address Low
	OUT	DmaCh1Addr	;Send low address
	INX	H
	MOV	A,M		;D ← Memory Address High
	OUT	DmaCh1Addr	;Send high address
{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.} 
	RIM
	ANI	InterruptEnbMsk	;A#0 => interrupts were enabled

{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 JMP whenever the floppy is NOT active. It will be JZ whenever the floppy IS active. If the floppy is active AND interrupts are enabled, we will disable interrupts.}
FloppyActiveSwitch1:
	DB	opJMP
	DW	StartCPDmaChannelNoDisable

{Come here if the floppy is running and interrupts are enabled.}
	DI

{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.}
	LDA	DmaActive
	MOV	D,A		;Save DmaActive in D
	ANI	DmaCh0Mask	;A ← 1 if floppy was busy
	OUT	DmaMode		;Disable channels
	IN	DmaStatus	;Read TerminalCount flags
	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	D	
	ORI	CPChannelMask
	OUT	DmaMode		;Start Dma
	STA	DmaActive	;Store back in DmaActive
	EI
	RET

StartCPDmaChannelNoDisable:
{Come here if the floppy is not running or if the floppy is running and interrupts are disabled.}
	DB	opMVIA		;A ← DmaActive
DmaActive:
	DB	DmaModeExtra
	MOV	D,A		;Save DmaActive in D
	ANI	DmaCh0Mask	;A ← 1 if floppy was busy
	OUT	DmaMode		;Disable channels
	IN	DmaStatus	;Read TerminalCount flags
	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	D	
	ORI	CPChannelMask
	OUT	DmaMode		;Start Dma
	STA	DmaActive	;Store back in DmaActive
	RET


ReadDmaCompletion:

{Read Dma Status, and update DmaActive. Return updated ~DmaActive masked by DmaChannelMask.
On entry:
    E = DmaChannel Mask
On exit:
    A = ~DmaActive AND DmaChannelMask, condition code is set}

	RIM
	ANI	InterruptEnbMsk	;A#0 => interrupts were enabled

{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 JMP whenever the floppy is NOT active. It will be JZ whenever the floppy IS active. If the floppy is active AND interrupts are enabled, we will disable interrupts.}
FloppyActiveSwitch2:
	DB	opJMP
	DW	ReadDmaCompletionNoDisable

{Come here if the floppy is running and interrupts are enabled.}
	DI
	LDA	DmaActive	;D ← DmaActive
	MOV	D,A
	IN	DmaStatus	;Get the completed channels
	CMA			;Form complement mask of completed channels
	ANA	D		;Clear channels in DmaActive
	STA	DmaActive	;Store back in DmaActive
	EI
	CMA
	ANA	E		;Mask channels of interest
	RET

ReadDmaCompletionNoDisable:
{Come here if the floppy is not running or if the floppy is running and interrupts are disabled.}
	LDA	DmaActive	;D ← DmaActive
	MOV	D,A
	IN	DmaStatus	;Get the completed channels
	CMA			;Form complement mask of completed channels
	ANA	D		;Clear channels in DmaActive
	STA	DmaActive	;Store back in DmaActive
	CMA
	ANA	E		;Mask channels of interest
	RET

ClearDmaChannel:
{Read the completed channels in DmaStatus, and remove from DmaActive. Remove the channels specified by DmaChannelMask and re-enable the Dma controller.   Note that a hardware channel is automatically cleared at a terminal count condition.
On entry: A = DmaChannel Mask}

	CMA			;Complement channel mask
	MOV	E,A		;E ← Complemented mask of channels to be disabled
	RIM
	ANI	InterruptEnbMsk	;A#0 => interrupts were enabled

{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 JMP whenever the floppy is NOT active. It will be JZ whenever the floppy IS active. If the floppy is active AND interrupts are enabled, we will disable interrupts.}
FloppyActiveSwitch3:
	DB	opJMP
	DW	ClearDmaChannelNoDisable

{Come here if the floppy is running and interrupts are enabled.}
	DI
	LDA	DmaActive
	ANA	E		;A ← mask of channels to be active
	MOV	D,A		;D ← DmaActive
	ANI	DmaCh0Mask
	OUT	DmaMode		;Disable Dma
	IN	DmaStatus	;Read Terminal Count Flags
	CMA			;A ← inverted Terminal Count Flags
	ANA	D		;Clear completed channels in DmaActive
	OUT	DmaMode		;Restart DMA
	STA	DmaActive	;Store back in DmaActive
	EI
	RET

ClearDmaChannelNoDisable:
{Come here if the floppy is not running or if the floppy is running and interrupts are disabled.}
	LDA	DmaActive
	ANA	E		;A ← mask of channels to be active
	MOV	D,A		;D ← DmaActive
	ANI	DmaCh0Mask
	OUT	DmaMode		;Disable Dma
	IN	DmaStatus	;Read Terminal Count Flags
	CMA			;A ← inverted Terminal Count Flags
	ANA	D		;Clear completed channels in DmaActive
	OUT	DmaMode		;Restart DMA
	STA	DmaActive	;Store back in DmaActive
	RET
;
{  Subroutine:  StartFloppyChannel [H,L:  POINTER to DmaControlBlock].
Set up the address, count, Dma function of the CP channel.
Enable the Dma Channel.  Note: TCS and EW are set in Mode automatically. 

  On entry:
    A = function: DmaRead or DmaWrite
    HL  =  pointer to Dma Control Block (format described above).
	byte 0: Byte count low,
	byte 1: Byte count high,
	byte 2: Memory Address Low,
	byte 3: Memory Address high,
}

StartDmaChannel:
StartFloppyChannel:
	MOV	E,M		;E ← low byte count
	INX	H		;Point to High count
	MOV	D,M		;D ← High byte count
	DCX	D		;Decrement for Dma controller
	ORA	D		;OR in Dma function
	MOV	D,A
	MOV	A,E
	OUT	DmaCh0Count	;Send low count
	MOV	A,D
	OUT	DmaCh0Count	;Send high count
	INX	H		;Point to Low address
	MOV	A,M		;E ← Memory Address Low
	OUT	DmaCh0Addr	;Send low address
	INX	H
	MOV	A,M		;D ← Memory Address High
	OUT	DmaCh0Addr	;Send high address
{Now we are ready to start the DMA for the Floppy 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 Floppy 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 before we read the Status Register.}
	LDA	DmaActive
	MOV	D,A		;Save DmaActive in D
	XRA	A
	OUT	DmaMode		;Disable channels
	IN	DmaStatus	;Read TerminalCount flags
	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	D	
	ORI	DmaCh0Mask
	OUT	DmaMode		;Start Dma
	STA	DmaActive	;Store back in DmaActive
	RET


	END	DmaSubs