packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/dma.mod

IMPLEMENTATION MODULE DMA;

	(********************************************************)
	(*							*)
	(*	Procedures to deal with Direct Memory Access	*)
	(*		    input and output.			*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	21 February 1995		*)
	(*  Status:		OK				*)
	(*							*)
	(********************************************************)

(************************************************************************)
(*									*)
(* HARDWARE ASSUMPTIONS:						*)
(*  DMA transfers to/from main memory are performed by the 8237A DMA	*)
(*  controller.  The DMA controller is at ports 000H-01FH.		*)
(*									*)
(*  A complicating factor is that the DMA controller expects physical	*)
(*  addresses (i.e. it knows nothing about memory segmentation), and	*)
(*  furthermore it can only handle 16-bit addresses.  To get around	*)
(*  this limitation, port 081H is a latch for the higher-order bits of	*)
(*  the address.  Unfortunately, this arrangement means that, when the	*)
(*  DMA controller increments the address during a transfer, any carry	*)
(*  from the low-order 16 bits is not propagated upwards.  The		*)
(*  practical consequence is that the user's data buffer must not cross	*)
(*  a 64 Kbyte boundary in main memory.					*)
(*									*)
(*  Added later: according to information I have just received, each	*)
(*  DMA channel has its own port for holding the high-order part of the	*)
(*  address.  This information has been incorporated into the current	*)
(*  version of the software, but has not been tested (14/2/91).		*)
(*									*)
(************************************************************************)

FROM SYSTEM IMPORT
    (* type *)	BYTE;

FROM LowLevel IMPORT
    (* proc *)	OutByte, Physical, LowByte, HighByte, LowWord, HighWord;

FROM MiscPMOS IMPORT
    (* proc *)	EnterCriticalSection, LeaveCriticalSection;

FROM Storage1 IMPORT
    (* proc *)	ALLOCATE, DEALLOCATE;

(************************************************************************)

TYPE CARD9 = ARRAY [0..8] OF CARDINAL;

CONST
    DMAchip = 0;
    LatchPort = CARD9 (87H, 83H, 81H, 82H, 00H, 8BH, 89H, 8AH, 8FH);

    (* Remark: note that channel 4 does not appear to have a latch	*)
    (* port.  Note also that I've added a latch port for "channel 8",	*)
    (* which is not in fact a DMA channel so far as I know but has been	*)
    (* given to me as the "refresh" port, and I've put it into this	*)
    (* list so that I won't forget the number in case I ever need it.	*)
    (* Note in any case that the present version of this module only	*)
    (* covers channels 0..3, so that the other numbers are here only as	*)
    (* a reminder for future enhancements to the module.		*)

(************************************************************************)
(*		      DEALING WITH THE 64K LIMITATION			*)
(************************************************************************)

PROCEDURE CheckDMAAddress (Address: ADDRESS;  count: CARDINAL): BOOLEAN;

    (* Returns TRUE iff the given address and count values are		*)
    (* suitable for a DMA transfer.  An unsuitable pair is one where	*)
    (* the transfer would cross a 64K boundary in memory - a case which	*)
    (* the DMA hardware cannot handle.					*)

    VAR first: LONGCARD;

    BEGIN
	first := Physical (Address);
	RETURN HighWord(first) = HighWord(first+LONGCARD(count-1));
    END CheckDMAAddress;

(************************************************************************)

PROCEDURE AllocateDMABuffer (VAR (*OUT*) Address: ADDRESS;  size: CARDINAL);

    (* Like the standard ALLOCATE procedure, but ensures that the	*)
    (* allocated memory does not straddle a 64K boundary.		*)

    VAR NotOK, temp: POINTER TO ADDRESS;

    BEGIN
	NotOK := NIL;  ALLOCATE (Address, size);
	WHILE (Address <> NIL) AND NOT CheckDMAAddress(Address, size) DO

	    (* This one didn't work out, put it on a list of buffers	*)
	    (* to be discarded later, and try again.			*)

	    temp := Address;  temp^ := NotOK;  NotOK := temp;
	    ALLOCATE (Address, size);

	END (*LOOP*);

	(* Deallocate all the buffers that didn't work out. *);

	WHILE NotOK <> NIL DO
	    temp := NotOK;  DEALLOCATE (NotOK, size);
	    NotOK := temp;
	END (*WHILE*);

    END AllocateDMABuffer;

(************************************************************************)
(*			SETTING UP THE DMA CONTROLLER			*)
(************************************************************************)

PROCEDURE LoadDMAparameters (channel, operation: CARDINAL;
				Address: ADDRESS; count: CARDINAL);

    (* Loads the DMA controller with the address and count values	*)
    (* as a preliminary to starting a disk transfer.			*)
    (* The code for "operation" is 0 for verify, 1 for read (i.e. from	*)
    (* device to memory), and 2 for write (from main memory to device).	*)
    (* Other values are illegal.					*)

    (* Note that this procedure requires critical section protection	*)
    (* because, even though the four DMA channels can operate in	*)
    (* parallel, there is only a single mode register and a single	*)
    (* "byte pointer" flipflop.  Semaphores are inappropriate for this	*)
    (* application because the procedure is sometimes called from	*)
    (* inside an interrupt handler.					*)

    CONST
	ChannelEnable = DMAchip + 0AH;
	ModeRegister = DMAchip + 0BH;
	ClearByteFlipFlop = DMAchip + 0CH;

    VAR PhysicalAddress: LONGCARD;
	bottom16bits: CARDINAL;
	AddressRegister, CountRegister: CARDINAL;
	savedPSW: WORD;
	
    BEGIN
	AddressRegister := DMAchip + 2*channel;
	CountRegister := AddressRegister + 1;

	savedPSW := EnterCriticalSection();

	(* Specify the operation to be performed.  The constant 40H	*)
	(* encodes the options: single transfer mode, autoincrementing	*)
	(* address, and no autoinitialisation.				*)

	OutByte (ModeRegister, BYTE(40H + 4*operation + channel));

	(* Clear the "byte pointer" flipflop in the DMA chip, to ensure	*)
	(* that the subsequent writes to the address and count ports	*)
	(* can be sent in a known order.  The actual value written to	*)
	(* this port is irrelevant.					*)

	OutByte (ClearByteFlipFlop, 0);

	(* Load the starting address as a physical address.  Only 16	*)
	(* bits of the physical address will fit in the DMA controller.	*)
	(* The high-order 4 bits must be loaded into a separate latch.	*)
	(* Error to watch out for: can't cross a 64KB boundary.  In the	*)
	(* present version we treat that as the caller's problem.	*)

	PhysicalAddress := Physical (Address);
	bottom16bits := LowWord (PhysicalAddress);

	OutByte (AddressRegister, LowByte (bottom16bits));
	OutByte (AddressRegister, HighByte (bottom16bits));
	OutByte (LatchPort[channel], LowByte (HighWord(PhysicalAddress)));

	(* Load the count register.  The value loaded is one less than	*)
	(* the actual desired count.					*)

	DEC (count);
	OutByte (CountRegister, LowByte(count));
	OutByte (CountRegister, HighByte(count));

	(* Ensure that this DMA channel is enabled, by clearing a bit	*)
	(* in the mask register.					*)

	OutByte (ChannelEnable, BYTE(channel));

	LeaveCriticalSection (savedPSW);

    END LoadDMAparameters;

(************************************************************************)

END DMA.