packages feed

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

IMPLEMENTATION MODULE Devices;

	(********************************************************)
	(*							*)
	(*		Support for device drivers.		*)
	(*							*)
	(*  The aim of this module is to a measure of device	*)
	(*  independence in I/O operations.  It provides a	*)
	(*  uniform I/O interface to all device drivers which	*)
	(*  choose to make themselves known to module Devices.	*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	21 February 1995		*)
	(*  Status:		Working.			*)
	(*							*)
	(********************************************************)

FROM SYSTEM IMPORT
    (* proc *)	ADR;

FROM Queues IMPORT
    (* type *)	Queue,
    (* proc *)	CreateQueue, AddToQueue, TakeFromQueue;

FROM Semaphores IMPORT
    (* proc *)	CreateSemaphore, DestroySemaphore, Signal, Wait;

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

FROM LowLevel IMPORT
    (* proc *)	Physical;

FROM TerminationControl IMPORT
    (* proc *)	Crash, SetTerminationProcedure;

IMPORT Str;

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

CONST CheckCode = 0C5E2H;

TYPE

    (* We allow external device names to be up to eight characters in	*)
    (* length, and default directory strings to be up to 64 characters	*)
    (* in length.  (This decision should perhaps be reviewed later.)	*)

    EightChar = ARRAY [0..7] OF CHAR;
    DirString = ARRAY [0..63] OF CHAR;

    (* The fields in a device descriptor are:				*)
    (*	  check		a constant, inserted to detect uninitialised	*)
    (*			pointers					*)
    (*	  UnitLimit	maximum unit number for this device		*)
    (*	  RequestQueue	the queue of pending I/O requests		*)
    (*	  capacity	the number of sectors, as supplied by procedure	*)
    (*			DeviceName, for each unit of this device	*)

    DeviceRec = RECORD
		    check: CARDINAL;
		    UnitLimit: CARDINAL;
		    RequestQueue: Queue;
		    capacity: ARRAY [0..0] OF BlockNumberType;
		END (*RECORD*);

    Device = POINTER TO DeviceRec;

    (* We maintain a binary structure to hold the equivalences between	*)
    (* external and internal device names.  Note that the information	*)
    (* kept here is associated with a (device, unit) pair; it cannot	*)
    (* simply be associated with a device, which is why we can't use	*)
    (* a DeviceRec record for this purpose.				*)

    TreePointer = POINTER TO NameRecord;
    NameRecord = RECORD
		    left, right: TreePointer;
		    string: EightChar;
		    devicecode: Device;
		    unitnumber: CARDINAL;
		    defaultdir: DirString;
		 END (*RECORD*);

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

VAR
    (* DummyDevice is the device code returned for any unknown device	*)
    (* name.  Any attempted operation on DummyDevice will return a	*)
    (* "no such device" error.						*)

    DummyDevice: Device;

    (* Names points to head of the structure which holds device names.	*)

    Names: TreePointer;

(************************************************************************)
(*			     ERROR HANDLER				*)
(************************************************************************)

PROCEDURE Error (VAR (*INOUT*) details: RequestBlock;  ErrorNo: ErrorCode);

    (* Fills in the error code in the "details" record, and signals	*)
    (* that the operation is complete.					*)

    BEGIN
	WITH details DO
	    Status := ErrorNo;
	    Signal (DoneSemaphorePointer^);
	END (*WITH*);
    END Error;

(************************************************************************)
(*	  MAPPING BETWEEN EXTERNAL NAMES AND DEVICE INFORMATION		*)
(************************************************************************)

PROCEDURE CopyString (src: ARRAY OF CHAR;  VAR (*OUT*) dest: ARRAY OF CHAR;
					VAR (*INOUT*) place: CARDINAL);

    (* Copies src to dest, starting at dest[place].  On exit place	*)
    (* has been updated to point to the trailing Nul, or beyond the end	*)
    (* of dest if there was no room for the trailing Nul.		*)

    CONST Nul = CHR(0);

    VAR j: CARDINAL;

    BEGIN
	j := 0;
	WHILE (j<=HIGH(src)) AND (place<=HIGH(dest)) AND (src[j] <> Nul) DO
	    dest[place] := src[j];  INC (j);  INC (place);
	END (*WHILE*);
	IF place <= HIGH(dest) THEN
	    dest[place] := Nul;
	END (*IF*);
    END CopyString;

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

PROCEDURE CopyAndPad (name: ARRAY OF CHAR): EightChar;

    (* Converts a character string to a fixed-length string with	*)
    (* trailing space characters.					*)

    VAR result: EightChar;  j: [0..8];

    BEGIN
	result := "        ";  j := 0;
	WHILE (j<8) AND (j<=HIGH(name)) AND (name[j] <> CHR(0)) DO
	    result[j] := name[j];  INC (j);
	END (*WHILE*);
	RETURN result;
    END CopyAndPad;

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

PROCEDURE Search (name: ARRAY OF CHAR;  VAR (*OUT*) device: Device;
			VAR (*OUT*) unit: CARDINAL;
			VAR (*OUT*) found: BOOLEAN);

    (* Searches the Names structure for a match with "name", and	*)
    (* returns the device and unit codes; or returns found = FALSE if	*)
    (* the name was not found.						*)

    VAR namestring: EightChar;  current: TreePointer;

    BEGIN
	namestring := CopyAndPad (name);  current := Names;  found := FALSE;
	LOOP
	    IF current = NIL THEN EXIT(*LOOP*) END (*IF*);
	    IF current^.string = namestring THEN
		found := TRUE;  EXIT(*LOOP*);
	    ELSIF Str.Compare (current^.string, namestring) < 0 THEN
		current := current^.left;
	    ELSE
		current := current^.right;
	    END (*IF*);
	END (*LOOP*);
	IF found THEN
	    device := current^.devicecode;
	    unit := current^.unitnumber;
	END (*IF*);
    END Search;

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

PROCEDURE SearchTree (T: TreePointer;  dev: Device;  unitno: CARDINAL)
								: TreePointer;

    (* Searches tree T structure for a match with (dev, unitno), and	*)
    (* returns a pointer to the record found; or returns NIL if the	*)
    (* (dev, unitno) pair was not found.				*)

    VAR result: TreePointer;

    BEGIN
	IF T = NIL THEN RETURN NIL;
	ELSIF SameDevice(T^.devicecode, dev)
			AND (T^.unitnumber = unitno) THEN RETURN T;
	ELSE
	    result := SearchTree (T^.left, dev, unitno);
	    IF result <> NIL THEN RETURN result;
	    ELSE RETURN SearchTree (T^.right, dev, unitno);
	    END (*IF*);
	END (*IF*);
    END SearchTree;

(************************************************************************)
(*		  PROCEDURES CALLED BY THE USER TASKS			*)
(************************************************************************)

PROCEDURE SameDevice (d1, d2: Device): BOOLEAN;

    (* Tests the condition d1 = d2.	*)

    BEGIN
	RETURN Physical(d1) = Physical(d2);
    END SameDevice;

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

PROCEDURE NullDevice (): Device;

    (* Returns the device code which this module uses internally to	*)
    (* mean "nonexistent device" or "unknown device".  I/O operations	*)
    (* on this device are of course impossible (if attempted, they will	*)
    (* result in the NoSuchDevice error code), but the device code can	*)
    (* be used by client modules as a marker to indicate that no	*)
    (* genuine device has yet been specified.				*)

    BEGIN
	RETURN DummyDevice;
    END NullDevice;

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

PROCEDURE IdentifyDevice (name: ARRAY OF CHAR;  VAR (*OUT*) device: Device;
				VAR (*OUT*) unitnumber: CARDINAL);

    (* Given the character string form of a device name, returns the	*)
    (* device code and unit number.					*)

    VAR found: BOOLEAN;

    BEGIN
	Search (name, device, unitnumber, found);
	IF NOT found THEN
	    device := DummyDevice;
	    unitnumber := 0;
	END (*IF*);
    END IdentifyDevice;

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

PROCEDURE GetDefaultDirectory (device: Device;  unit: CARDINAL;
				VAR (*OUT*) DirectoryString: ARRAY OF CHAR);

    (* Produces a string representing the default directory.  The	*)
    (* string includes the character form of the device name.		*)

    VAR T: TreePointer;  j: CARDINAL;

    BEGIN
	T := SearchTree (Names, device, unit);
	IF T = NIL THEN
	    DirectoryString[0] := CHR(0);
	ELSE
	    WITH T^ DO
		j := 0;
		CopyString (string, DirectoryString, j);
		WHILE DirectoryString[j-1] = ' ' DO
		    DEC (j);
		END (*WHILE*);
		CopyString (":\", DirectoryString, j);
		CopyString (defaultdir, DirectoryString, j);
	    END (*WITH*);
	END (*IF*);
    END GetDefaultDirectory;

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

PROCEDURE VolumeSize (device: Device;  unitnumber: CARDINAL): BlockNumberType;

    (* Returns the number of sectors on (device, unitnumber).  Note	*)
    (* that the result is meaningful only for device drivers which	*)
    (* supply this information (see DeviceName below).			*)

    BEGIN
(*# save, check(index => off) *)
	RETURN device^.capacity[unitnumber];
(*# restore *)
    END VolumeSize;

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

PROCEDURE IOrequest (VAR (*INOUT*) details: RequestBlock);

    (* Adds the requested I/O operation to the queue of pending		*)
    (* operations on details.device.  The operation might not be done	*)
    (* immediately since the device driver might still be working on an	*)
    (* earlier request.  On I/O completion, the device driver performs	*)
    (* a Signal(details.DoneSemaphorePointer^).				*)

    BEGIN
	WITH details DO
	    IF device^.check <> CheckCode THEN
		Error (details, NoSuchDevice);
	    ELSIF unit > device^.UnitLimit THEN
		Error (details, NoSuchUnit);
	    ELSE
		AddToQueue (device^.RequestQueue, ADR(details));
	    END (*IF*);
	END (*WITH*);
    END IOrequest;

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

PROCEDURE SynchronousIO (op: OperationType;
			d: Device;  unitnumber: CARDINAL;
			Block: BlockNumberType;
			MemoryAddress: ADDRESS;  amount: CARDINAL): ErrorCode;

    (* Like IOrequest, but does not return until the operation is	*)
    (* complete.  This is for the convenience of callers who do not	*)
    (* want to deal with the complications of semaphores.		*)

    VAR RequestRecord: RequestBlock;
	DoneSemaphore: Semaphore;
	status: ErrorCode;

    BEGIN
	CreateSemaphore (DoneSemaphore, 0);
	WITH RequestRecord DO
	    operation := op;
	    device := d;
	    unit := unitnumber;
	    BlockNumber := Block;
	    BufferAddress := MemoryAddress;
	    ByteCount := amount;
	    DoneSemaphorePointer := ADR (DoneSemaphore);
	END (*WITH*);
	IOrequest (RequestRecord);
	Wait (DoneSemaphore);
	status := RequestRecord.Status;
	DestroySemaphore (DoneSemaphore);
	RETURN status;
    END SynchronousIO;

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

PROCEDURE BlockRead (d: Device;  unitnumber: CARDINAL;
			BlockNumber: BlockNumberType;
			MemoryAddress: ADDRESS;  amount: CARDINAL): ErrorCode;

    (* Reads "amount" bytes into memory, starting at the beginning of	*)
    (* block number "BlockNumber" on the specified device.		*)

    BEGIN
	RETURN SynchronousIO (read, d, unitnumber, BlockNumber,
					MemoryAddress, amount);
    END BlockRead;

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

PROCEDURE ReadPhysicalBlock (d: Device;  unitnumber: CARDINAL;
		BlockNo: BlockNumberType;  MemoryAddress: ADDRESS): ErrorCode;

    (* Reads one sector into memory, starting at the beginning of	*)
    (* physical block number "BlockNo" on the specified device.		*)

    BEGIN
	RETURN SynchronousIO (physicalread, d, unitnumber, BlockNo,
							MemoryAddress, 0);
    END ReadPhysicalBlock;

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

PROCEDURE BlockWrite (d: Device;  unitnumber: CARDINAL;
			BlockNumber: BlockNumberType;
			MemoryAddress: ADDRESS;  amount: CARDINAL): ErrorCode;

    (* Writes "amount" bytes from memory to the device, starting at the	*)
    (* beginning of block number "BlockNumber" on the specified device.	*)

    BEGIN
	RETURN SynchronousIO (write, d, unitnumber, BlockNumber,
					MemoryAddress, amount);
    END BlockWrite;

(************************************************************************)
(*		PROCEDURES CALLED BY THE DEVICE DRIVERS			*)
(************************************************************************)

PROCEDURE InstallDeviceDriver (MaxUnitNumber: CARDINAL): Device;

    (* Called by the device driver as part of its initialization code,	*)
    (* to make the driver known to this module.  MaxUnitNumber is the	*)
    (* maximum unit number which this device driver will support (0 for	*)
    (* a single-unit device driver).  The other input parameter is	*)
    (* the external name of the device.  The value returned is the	*)
    (* means by which the device driver will henceforth identify itself	*)
    (* to this module.							*)

    VAR result: Device;  j: CARDINAL;

    BEGIN
	ALLOCATE (result, SIZE(DeviceRec)+MaxUnitNumber*SIZE(BlockNumberType));
	WITH result^ DO
	    check := CheckCode;
	    UnitLimit := MaxUnitNumber;
	    CreateQueue (RequestQueue);
	    FOR j := 0 TO MaxUnitNumber DO
(*# save, check(index => off) *)
		capacity[j] := 0;
(*# restore *)
	    END (*FOR*);
	END (*WITH*);
	RETURN result;
    END InstallDeviceDriver;

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

PROCEDURE DeviceName (device: Device;  unit: CARDINAL;
			name: ARRAY OF CHAR;  size: BlockNumberType;
			DefaultDirString: ARRAY OF CHAR);

    (* Specifies an external name for a given device and unit number.	*)
    (* Duplicate names are permitted.  The size parameter gives the	*)
    (* number of blocks in the partition if this "device" is actually	*)
    (* a partition on a hard disk; but it is not necessarily meaningful	*)
    (* for other device types.  (This parameter is supplied only for	*)
    (* the benefit of the file system when it has to deal with the	*)
    (* special case of a large partition.)  Device drivers which are	*)
    (* not prepared to specify a meaningful size should supply a value	*)
    (* of 0 for the size.						*)
    (* DefaultDirString specifies the initial default directory.	*)

    VAR namestring: EightChar;  current, newptr: TreePointer;
	inserted: BOOLEAN;  j: CARDINAL;

    BEGIN
	(* For a faulty device driver, cause a crash - it's better to	*)
	(* get the crash at startup time than to let the program run	*)
	(* with a suspect driver.					*)

	IF (device = NIL) OR (device^.check <> CheckCode) THEN
	    Crash ("Faulty device driver");
	END(*IF*);

	(* Insert the size information into the device descriptor. *)

	WITH device^ DO
	    IF unit < UnitLimit THEN
(*# save, check(index => off) *)
		capacity[unit] := size;
(*# restore *)
	    END (*IF*);
	END (*WITH*);

	(* Create a NameRecord to hold the remaining information. *)

	namestring := CopyAndPad (name);
	NEW (newptr);
	WITH newptr^ DO
	    left := NIL;  right := NIL;  string := namestring;
	    devicecode := device;  unitnumber := unit;
	    j := 0;  CopyString (DefaultDirString, defaultdir, j);
	END (*WITH*);

	(* Insert the new record into the Names tree.	*)

	IF Names = NIL THEN
	    Names := newptr;
	ELSE
	    current := Names;  inserted := FALSE;
	    REPEAT
		IF Str.Compare (current^.string, namestring) < 0 THEN
		    IF current^.left = NIL THEN
			current^.left := newptr;  inserted := TRUE;
		    ELSE
			current := current^.left;
		    END (*IF*);
		ELSE
		    IF current^.right = NIL THEN
			current^.right := newptr;  inserted := TRUE;
		    ELSE
			current := current^.right;
		    END (*IF*);
		END (*IF*);
	    UNTIL inserted;
	END (*IF*);
    END DeviceName;

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

PROCEDURE AcceptRequest (device: Device): RequestBlockPointer;

    (* Returns a pointer to the next request enqueued for this device.	*)
    (* If there is no next request, we wait until one appears.		*)

    BEGIN
	RETURN TakeFromQueue (device^.RequestQueue);
    END AcceptRequest;

(************************************************************************)
(*			TERMINATION HANDLER				*)
(************************************************************************)

PROCEDURE DeleteTree (VAR (*INOUT*) T: TreePointer);

    (* Destroys tree T.	*)

    BEGIN
	IF T <> NIL THEN
	    DeleteTree (T^.left);  DeleteTree (T^.right);
	    DISPOSE (T);
	END (*IF*);
    END DeleteTree;

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

PROCEDURE CloseDown ();

    (* Cleans up at program termination.  Note that all device drivers	*)
    (* will have closed down by the time this procedure is called, as	*)
    (* they are higher-level modules.					*)
    (* Remark: have not yet made provision for destroying the device	*)
    (* descriptors.							*)

    BEGIN
	DeleteTree (Names);
    END CloseDown;

(************************************************************************)
(*			    INITIALISATION				*)
(************************************************************************)

BEGIN
    Names := NIL;
    SetTerminationProcedure (CloseDown);
    NEW (DummyDevice);
    WITH DummyDevice^ DO
	check := 0;
	UnitLimit := 0;
	capacity[0] := 0;
	CreateQueue (RequestQueue);
    END (*WITH*);
END Devices.