packages feed

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

IMPLEMENTATION MODULE HardDisk;

	(********************************************************)
	(*							*)
	(*		Device driver for hard disk		*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	26 February 1995		*)
	(*  Status:		Working				*)
	(*	This module has been implicated in a number of	*)
	(*	mysterious bugs, but every time I've tried to	*)
	(*	track down problems they've turned out to be	*)
	(*	artefacts of VID or unfortunate interactions	*)
	(*	with SmartDrive.  Although it's hard to be	*)
	(*	completely sure that the module is bug-free,	*)
	(*	I'm reasonably certain that it is working as	*)
	(*	intended.					*)
	(*							*)
	(*	Note however that the safety of this module is	*)
	(*	suspect in an environment where the Windows	*)
	(*	SmartDrive program is running.			*)
	(*							*)
	(*	Note also that OS/2 will prevent the		*)
	(*	initialisation phase of this module from	*)
	(*	reading the partition table; this will cause	*)
	(*	the module to conclude that there are no	*)
	(*	logical drives, which of course means that	*)
	(*	the module does not do anything useful		*)
	(*	under OS/2.					*)
	(*							*)
	(********************************************************)

(************************************************************************)
(*									*)
(* The hardware interface is an array of 8 ports, starting at port 1F0H,*)
(* as follows:								*)
(*	0	read or write data					*)
(*	1	set precompensation cylinder, when written to		*)
(*		error register, when read				*)
(*	2	sector count						*)
(*	3	sector number						*)
(*	4	cylinder number (bottom 8 bits)				*)
(*	5	cylinder number (top 2 bits)				*)
(*	6	ECC/size/drive/head, stored as:				*)
(*			bits 3-0	head				*)
(*			bit 4		drive				*)
(*			bits 6, 5	sector size code: 00 for 256	*)
(*					bytes, 01 for 512, 10 for 1024,	*)
(*					11 for 128-byte sectors		*)
(*			bit 7		extend length to include ECC	*)
(*	7	command register, when written to			*)
(*		status register, when read				*)
(*									*)
(* The status register bits have the meaning:				*)
(*		bit 7	controller busy					*)
(*		bit 6	drive ready					*)
(*		bit 5	write fault					*)
(*		bit 4	seek complete					*)
(*		bit 3	data request					*)
(*		bit 2	data error corrected from ECC			*)
(*		bit 1	command in progress				*)
(*		bit 0	status error, see error register		*)
(*									*)
(* The bit definitions for the error register are shown in procedure	*)
(* CheckStatus.								*)
(*									*)
(* There is also a control byte at port 3F6H, for which we set bit 3	*)
(* to specify >8 heads; in addition setting bit 6 or bit 7 of this	*)
(* byte will disable retries.						*)
(*									*)
(* Interrupts are INT 76H, which is IRQ level 14, i.e. it is through	*)
(* the slave interrupt controller.					*)
(*									*)
(************************************************************************)

FROM SYSTEM IMPORT
    (* type *)	BYTE, ADDRESS;

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

FROM IOErrorCodes IMPORT
    (* type *)	ErrorCode;

FROM LowLevel IMPORT
    (* proc *)	InByte, OutByte, InStringWord, OutStringWord,
		IANDB, IOR, LowByte, HighByte,
		MakePointer, AddOffset;

FROM MiscPMOS IMPORT
    (* type *)	RegisterPacket,
    (* proc *)	BIOS, EnterCriticalSection, LeaveCriticalSection;

FROM Semaphores IMPORT
    (* type *)	Semaphore,
    (* proc *)	CreateSemaphore, Signal;

FROM Timer IMPORT
    (* proc *)	TimedWait;

FROM TaskControl IMPORT
    (* proc *)	CreateTask, WaitForInterrupt, CreateInterruptTask;

FROM Devices IMPORT
    (* type *)	BlockNumberType, Device, RequestBlock, RequestBlockPointer,
		OperationType,
    (* proc *)	InstallDeviceDriver, DeviceName, AcceptRequest;

FROM DMA IMPORT
    (* proc *)	CheckDMAAddress;

FROM Windows IMPORT
    (* type *)	Window, Colour, FrameType, DividerType,
    (* proc *)	OpenWindow, WriteString, ChangeScrollingRegion;

FROM MaintenancePages IMPORT
    (* type *)	MaintenancePage,
    (* proc *)	CreateMaintenancePage, Associate;

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

CONST
    testing = TRUE;		(* controls output to maintenance page *)
    DataPort = 01F0H;
    StatusPort = DataPort+7;
    DiskInterrupt = 76H;
    BlockSize = 512;		(* bytes per sector *)

TYPE
    (* The following declarations reflect the fact that a hard disk	*)
    (* may be partitioned into several logical drives, each of which	*)
    (* looks like a separate drive to the programmer.			*)

    PhysicalDriveNumber = [0..1];
    LogicalDriveNumber = [0..7];

    CommandArray = ARRAY [1..7] OF BYTE;

    (* Because we have to allow for a variety of disk types, there is a	*)
    (* fixed disk parameter table where each entry describes one	*)
    (* supported disk type.  Interrupt vector 41H (address 104H) is a	*)
    (* pointer to the table entry for drive 0, and vector 46H (address	*)
    (* 118H) points to the table entry for drive 1.  The table entry is	*)
    (* 16 bytes long:							*)
    (*	max # of cylinders	: CARDINAL;				*)
    (*	max # of heads		: BYTE;					*)
    (*	unused			: WORD;					*)
    (*	starting write precompensation cylinder: WORD;			*)
    (*	max ECC data burst length: BYTE;				*)
    (*	control byte		: BYTE;					*)
    (*					bit 7 disables retries -or-	*)
    (*					bit 6 disables retries		*)
    (*					bit 3 means more than 8 heads	*)
    (*	unused:			: ARRAY [1..3] OF BYTE;			*)
    (*	landing zone:		: WORD;					*)
    (*	sectors per track:	: BYTE;					*)
    (*	reserved		: BYTE					*)
    (* The relevant parts of this table are copied by the module	*)
    (* initialisation code into PhysicalDriveData.  Note that a lot of	*)
    (* this information is never used by this module.			*)

    DiskParameterTable = RECORD
			    maxcylinders: CARDINAL;
			    maxheads: BYTE;
			    dummy1: CARDINAL;		(* unused *)
			    precomp: CARDINAL;
			    maxECCburstlength: BYTE;
			    optionbyte: BYTE;
			    dummy2: ARRAY [1..3] OF BYTE; (* unused *)
			    landingzone: CARDINAL;
			    sectorspertrack: BYTE;
			    dummy3: BYTE;		(* unused *)
			 END (*RECORD*);

VAR
    log: Window;		(* for maintenance page output *)
    harddisk: Device;
    DataRequest: Semaphore;
    NumberOfPhysicalDrives: [0..MAX(PhysicalDriveNumber)+1];
    NumberOfLogicalDrives: [0..MAX(LogicalDriveNumber)+1];

    PhysicalDriveData: ARRAY PhysicalDriveNumber OF
			    RECORD
				SectorsPerTrack: CARDINAL;
				NumberOfHeads: CARDINAL;
				PrecompensationCylinder: CARDINAL;
				ExtraHeadOption: BYTE;
			    END (*RECORD*);

    LogicalUnit: ARRAY LogicalDriveNumber OF
			    RECORD
				physicaldrive: PhysicalDriveNumber;
				bootblock: BlockNumberType;
				numberofsectors: BlockNumberType;
			    END (*RECORD*);

(************************************************************************)
(*			HARDWARE STATUS CHECKS				*)
(************************************************************************)

PROCEDURE TestReady (VAR (*IN*) CommandBlock: CommandArray): ErrorCode;

    (* Checks that both the controller and the drive are ready to	*)
    (* accept a command.						*)

    CONST lots = 32767;

    VAR patience: CARDINAL;

    BEGIN

	(* First test the controller status byte, looping until the	*)
	(* busy bit is clear or until we run out of patience.		*)

	patience := lots;
	WHILE ORD(IANDB (InByte (StatusPort), 80H)) <> 0 DO
	    DEC (patience);
	    IF patience = 0 THEN
		RETURN ControllerNotListening;
	    END(*IF*);
	END (*WHILE*);

	(* Put out the ECC/size/drive/head code, to check whether the	*)
	(* drive is ready.						*)

	OutByte (DataPort+6, CommandBlock[6]);

	IF ORD(IANDB (InByte(StatusPort), 40H)) = 0 THEN
	    RETURN DriveNotReady;
	END (*IF*);

	RETURN OK;
    END TestReady;

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

PROCEDURE CheckStatus (): ErrorCode;

    (* Returns the current status of the disk controller.	*)
    (* N.B. Reports OK if the controller is still busy.		*)

    VAR StatusByte: BYTE;

    BEGIN
	StatusByte := InByte (StatusPort);
	IF ORD(IANDB (StatusByte, 80H)) <> 0 THEN RETURN OK;
	ELSIF ORD(IANDB (StatusByte, 20H)) <> 0 THEN RETURN WriteFault;
	ELSIF ORD(IANDB (StatusByte, 40H)) = 0 THEN RETURN DriveNotReady;
	ELSIF ORD(IANDB (StatusByte, 10H)) = 0 THEN RETURN SeekFailure;

	(* If the low-order bit of the status register is set, we need	*)
	(* to look at the error register.				*)

	ELSIF ODD (StatusByte) THEN

	    StatusByte := InByte (DataPort+1);
	    IF ORD(IANDB(StatusByte,80H)) <> 0 THEN RETURN BadBlock
	    ELSIF ORD(IANDB(StatusByte,40H)) <> 0 THEN RETURN BadData
	    ELSIF ORD(IANDB(StatusByte,20H)) <> 0 THEN RETURN UndiagnosedFailure
	    ELSIF ORD(IANDB(StatusByte,10H)) <> 0 THEN RETURN SectorNotFound
	    ELSIF ORD(IANDB(StatusByte,08H)) <> 0 THEN RETURN UndiagnosedFailure
	    ELSIF ORD(IANDB(StatusByte,04H)) <> 0 THEN RETURN BadCommand
	    ELSIF ORD(IANDB(StatusByte,02H)) <> 0 THEN RETURN CalibrationFailure
	    ELSIF ORD(IANDB(StatusByte,01H)) <> 0 THEN RETURN BadData
	    ELSE RETURN OK;
	    END (*IF*);

	ELSE RETURN OK;
	END (*IF*);

    END CheckStatus;

(************************************************************************)
(*		  SENDING A COMMAND TO THE DISK CONTROLLER		*)
(************************************************************************)

PROCEDURE Command (VAR (*IN*) CommandBlock: CommandArray): ErrorCode;

    (* Outputs the command block.  *)

    VAR patience: CARDINAL;
	status: ErrorCode;
	j: [1..7];

    BEGIN
	patience := 1000;
	LOOP
	    status := TestReady (CommandBlock);
	    IF status = OK THEN EXIT (*LOOP*) END (*IF*);
	    IF status = ControllerNotListening THEN
		RETURN status;
	    END (*IF*);
	    DEC (patience);
	    IF patience = 0 THEN RETURN status; END (*IF*);
	END (*LOOP*);

	(* Send the command block to the device interface.	*)

	FOR j := 1 TO 7 DO
	    OutByte (DataPort+j, CommandBlock[j]);
	END (*FOR*);

	RETURN OK;

    END Command;

(************************************************************************)
(*			    THE INTERRUPT TASK				*)
(************************************************************************)

PROCEDURE DRQInterruptHandler;

    (* Performs a Signal(DataRequest) on each hardware interrupt.	*)

    BEGIN
	LOOP (*FOREVER*)
	    WaitForInterrupt;
	    Signal (DataRequest);
	END (*LOOP*);
    END DRQInterruptHandler;

(************************************************************************)
(*			CHECK FOR DISK READY				*)
(************************************************************************)

PROCEDURE WaitDRQ (VAR (*OUT*) TimedOut: BOOLEAN);

    (* Busy waits for a data request. *)

    VAR patience: CARDINAL;

    BEGIN
	patience := 100H;  TimedOut := FALSE;
	LOOP
	    IF ORD(IANDB (InByte(StatusPort), 8)) <> 0 THEN
		EXIT(*LOOP*);
	    END(*IF*);
	    DEC(patience);
	    IF patience = 0 THEN
		TimedOut := TRUE;
		EXIT (*LOOP*);
	    END (*IF*);
	END (*LOOP*);
    END WaitDRQ;

(************************************************************************)
(*		   THE ACTUAL READ AND WRITE OPERATIONS			*)
(************************************************************************)

PROCEDURE ReadOperation (VAR (*INOUT*) CommandBlock: CommandArray;
					BufferAddress: ADDRESS): ErrorCode;

    (* Data transfer from disk to DataBuffer, where CommandBlock	*)
    (* specifies the precise operation desired.				*)

    CONST largenumber = 1000;

    VAR status: ErrorCode;  TimedOut: BOOLEAN;
	sectors: SHORTCARD;

    BEGIN
	(* Check for 64K boundary error. *)

	IF NOT CheckDMAAddress (BufferAddress,
				BlockSize*VAL(CARDINAL,CommandBlock[2])) THEN
	    RETURN BadDMAAddress;
	END (*IF*);

	status := Command (CommandBlock);
	IF status <> OK THEN
	    RETURN status;
	END (*IF*);

	sectors := CommandBlock[2];
	REPEAT
	    TimedWait (DataRequest, largenumber, TimedOut);

	    IF TimedOut THEN
		RETURN TimeoutError;
	    END (*IF*);

	    (* Get the sector, via block input *)

	    InStringWord (DataPort, BufferAddress, 256 (*words*) );
	    BufferAddress := AddOffset (BufferAddress, BlockSize);

	    status := CheckStatus();
	    IF status <> OK THEN
		RETURN status;
	    END (*IF*);
	    DEC (sectors);

	UNTIL sectors = 0;
	RETURN OK;

    END ReadOperation;

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

PROCEDURE WriteOperation (VAR (*INOUT*) CommandBlock: CommandArray;
					BufferAddress: ADDRESS): ErrorCode;

    (* Writes a block of data from BufferAddress^.  CommandBlock	*)
    (* specifies the disk block, amount of data to transfer, etc.	*)

    CONST largenumber = 1000;

    VAR status: ErrorCode;  TimedOut: BOOLEAN;

    BEGIN
	(* Check for 64K boundary error. *)

	IF NOT CheckDMAAddress (BufferAddress,
				BlockSize*VAL(CARDINAL,CommandBlock[2])) THEN
	    RETURN BadDMAAddress;
	END (*IF*);

	status := Command (CommandBlock);
	IF status <> OK THEN
	    RETURN status;
	END (*IF*);
	WaitDRQ (TimedOut);
	IF TimedOut THEN RETURN TimeoutError END (*IF*);

	REPEAT
	    (* Put the sector, via block output *)

	    OutStringWord (DataPort, BufferAddress, 256 (*words*) );
	    BufferAddress := AddOffset (BufferAddress, BlockSize);

	    TimedWait (DataRequest, largenumber, TimedOut);
	    IF TimedOut THEN RETURN TimeoutError END (*IF*);
	    IF CheckStatus() <> OK THEN RETURN status END (*IF*);

	    (* Remark: the termination test below is a check on the	*)
	    (* data request bit of the status register.  When the data	*)
	    (* request bit becomes 0, we know that there are no further	*)
	    (* sectors to transfer.					*)

	UNTIL ORD(IANDB(InByte (StatusPort), 8)) = 0;
	RETURN OK;

    END WriteOperation;

(************************************************************************)
(*			SETTING UP THE COMMAND BLOCK			*)
(************************************************************************)

PROCEDURE Decompose (BlockNumber: BlockNumberType;  drive: PhysicalDriveNumber;
			VAR (*OUT*) head, cylinder, sector: CARDINAL);

    (* Translates a block number into head/cylinder/sector coordinates.	*)
    (* The relationship is						*)
    (*	BlockNumber = SectorsPerTrack*(NumberOfHeads*cylinder+head)	*)
    (*						+ sector - 1		*)
    (* Note that sector number starts from 1, while the numbering	*)
    (* of blocks, heads, and cylinders all start from 0.		*)

    VAR H: CARDINAL;

    BEGIN
	WITH PhysicalDriveData[drive] DO
	    sector := CARDINAL(BlockNumber MOD LONGCARD(SectorsPerTrack)) + 1;
	    cylinder := CARDINAL(BlockNumber DIV LONGCARD(SectorsPerTrack));
	    H := NumberOfHeads;
	END (*WITH*);
	head := cylinder MOD H;
	cylinder := cylinder DIV H;
    END Decompose;

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

PROCEDURE Encode (VAR (*OUT*) CommandBlock: CommandArray;  opcode: BYTE;
			drive: PhysicalDriveNumber;
			BlockNumber: BlockNumberType; count: CARDINAL);

    (* Given the drive number, block number, and byte count of a	*)
    (* disk operation, fills in the appropriate CommandBlock fields.	*)

    VAR sector, cylinder, head: CARDINAL;

    BEGIN

	(* Deal with the write precompensation cylinder, the "extra	*)
	(* head" option, and retry suppression.				*)

	WITH PhysicalDriveData[drive] DO
	    CommandBlock[1] := SHORTCARD(PrecompensationCylinder DIV 4);
	    OutByte (3F6H, ExtraHeadOption);
	    IF ORD(IANDB (ExtraHeadOption, 0C0H)) <> 0 THEN
		INC (opcode);			(* to disable retries *)
	    END (*IF*);
	END (*WITH*);

	(* CommandBlock[2] specifies the number of sectors.	*)

	CommandBlock[2] := SHORTCARD(count DIV BlockSize);

	(* Turn the block number into head/cylinder/sector form. *)

	Decompose (BlockNumber, drive, head, cylinder, sector);
	CommandBlock[3] := SHORTCARD(sector);
	CommandBlock[4] := LowByte (cylinder);
	CommandBlock[5] := HighByte (cylinder);		(* only 2 bits *)

	(* The 0A0H below is the code for ECC and 512-byte sectors.	*)

	CommandBlock[6] := BYTE(16*drive + head + 0A0H);
	CommandBlock[7] := opcode;

    END Encode;

(************************************************************************)
(*		    THE USER-CALLABLE READ OPERATIONS			*)
(************************************************************************)

PROCEDURE ReadPhysicalBlock (drive: PhysicalDriveNumber;
			BlockNumber: BlockNumberType;  count: CARDINAL;
			BufferAddress: ADDRESS): ErrorCode;

    (* Reads "count" bytes, starting at the beginning of disk block	*)
    (* "BlockNumber" on the specified drive, into a memory buffer.	*)

    VAR CommandBlock: CommandArray;  result: ErrorCode;

    BEGIN
	IF drive >= NumberOfPhysicalDrives THEN
	    result := NoSuchUnit;
	ELSE
	    Encode (CommandBlock, 20H, drive, BlockNumber, count);
	    result := ReadOperation (CommandBlock, BufferAddress);
	END (*IF*);
	RETURN result;
    END ReadPhysicalBlock;

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

PROCEDURE ReadLogicalBlock (unit: LogicalDriveNumber;
			RelativeBlock: BlockNumberType;  count: CARDINAL;
			BufferAddress: ADDRESS): ErrorCode;

    (* Reads "count" bytes, starting at the beginning of disk block	*)
    (* "RelativeBlock" on the specified logical unit, into memory	*)
    (* starting at "BufferAddress".					*)

    VAR drive: PhysicalDriveNumber;  BlockNo: BlockNumberType;

    BEGIN
	WITH LogicalUnit[unit] DO
	    IF RelativeBlock >= numberofsectors THEN
		RETURN IllegalBlockNumber;
	    END (*IF*);
	    drive := physicaldrive;
	    BlockNo := bootblock + RelativeBlock;
	END (*WITH*);
	RETURN ReadPhysicalBlock (drive, BlockNo, count, BufferAddress);
    END ReadLogicalBlock;

(************************************************************************)
(*		    THE USER-CALLABLE WRITE OPERATIONS			*)
(************************************************************************)

PROCEDURE WritePhysicalBlock (drive: PhysicalDriveNumber;
				BlockNumber: BlockNumberType;  count: CARDINAL;
				BufferAddress: ADDRESS): ErrorCode;

    (* Writes "count" bytes, starting at the beginning of disk block	*)
    (* "BlockNumber" on the specified drive, from a memory buffer.	*)

    VAR CommandBlock: CommandArray;  result: ErrorCode;

    BEGIN
	IF drive >= NumberOfPhysicalDrives THEN
	    result := NoSuchUnit;
	ELSE
	    Encode (CommandBlock, 30H, drive, BlockNumber, count);
	    result := WriteOperation (CommandBlock, BufferAddress);
	END (*IF*);
	RETURN result;
    END WritePhysicalBlock;

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

PROCEDURE WriteLogicalBlock (unit: LogicalDriveNumber;
			RelativeBlock: BlockNumberType;  count: CARDINAL;
			BufferAddress: ADDRESS): ErrorCode;

    (* Writes "count" bytes, starting at the beginning of disk block	*)
    (* "RelativeBlock" on the specified logical unit, from memory	*)
    (* starting at "BufferAddress".					*)

    VAR drive: PhysicalDriveNumber;  BlockNo: BlockNumberType;

    BEGIN
	WITH LogicalUnit[unit] DO
	    IF RelativeBlock >= numberofsectors THEN
		RETURN IllegalBlockNumber;
	    END (*IF*);
	    drive := physicaldrive;
	    BlockNo := bootblock + RelativeBlock;
	END (*WITH*);
	RETURN WritePhysicalBlock (drive, BlockNo, count, BufferAddress);
    END WriteLogicalBlock;

(************************************************************************)
(*		     INTERFACE TO "Devices" MODULE			*)
(************************************************************************)

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

    (* Performs a disk transfer as specified in the request block.	*)

    BEGIN
	WITH details DO
	    IF operation = read THEN
		Status := ReadLogicalBlock (unit, BlockNumber,
						ByteCount, BufferAddress);
	    ELSIF operation = write THEN
		Status := WriteLogicalBlock (unit, BlockNumber,
						ByteCount, BufferAddress);
	    ELSIF operation = physicalread THEN
		Status := ReadPhysicalBlock (LogicalUnit[unit].physicaldrive,
					BlockNumber, BlockSize, BufferAddress);
	    ELSIF operation = physicalwrite THEN
		Status := WritePhysicalBlock (LogicalUnit[unit].physicaldrive,
					BlockNumber, BlockSize, BufferAddress);
	    ELSE
		Status := FeatureNotImplemented;
	    END (*IF*);
	END (*WITH*);
    END DiskOperation;

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

PROCEDURE DiskRequestHandler;

    (* This procedure runs as an independent task.  Each time around	*)
    (* its main loop, it picks up one enqueued I/O request and executes	*)
    (* the request.  The requests are placed on the queue by calls to	*)
    (* module Devices.  On completion of an operation, we inform the	*)
    (* caller by performing a Signal on a user-specified semaphore.	*)

    CONST MaxNumberOfRetries = 3;

    VAR reply: ErrorCode;  retries: CARDINAL;
	RequestPointer: RequestBlockPointer;

    BEGIN
	LOOP
	    RequestPointer := AcceptRequest (harddisk);
	    WITH RequestPointer^ DO
		IF operation = shutdown THEN
		    Signal (DoneSemaphorePointer^);
		    EXIT (*LOOP*);
		END (*IF*);
	    END (*WITH*);
	    retries := 0;
	    REPEAT
		DiskOperation (RequestPointer^);
		reply := RequestPointer^.Status;
		INC (retries);
	    UNTIL (reply = OK) OR (retries>MaxNumberOfRetries);
	    Signal (RequestPointer^.DoneSemaphorePointer^);

	END (*LOOP*);

    END DiskRequestHandler;

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

PROCEDURE SetUpDrive (d: PhysicalDriveNumber;  pplace: CARDINAL);

    (* Sets up PhysicalDriveData[d] by copying the appropriate		*)
    (* information from a table which already exists and whose address	*)
    (* is held at absolute location pplace in low memory.		*)

    (*# save, data(near_ptr=>off) *)

    TYPE TblPtr = POINTER TO DiskParameterTable;

    VAR TablePointer: TblPtr;
	PointerPointer: POINTER TO TblPtr;

    (*# restore *)

    BEGIN

	PointerPointer := MakePointer (0, pplace);
	TablePointer := PointerPointer^;
	IF TablePointer = FarNIL THEN
	    RETURN;
	END (*IF*);
	INC (NumberOfPhysicalDrives);

	WITH PhysicalDriveData[d] DO
	    SectorsPerTrack := CARDINAL(TablePointer^.sectorspertrack);
	    NumberOfHeads := CARDINAL(TablePointer^.maxheads);
	    PrecompensationCylinder := TablePointer^.precomp;
	    ExtraHeadOption := TablePointer^.optionbyte;
	END (*WITH*);

    END SetUpDrive;

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

PROCEDURE DecodePartitionTable;

    (* On entry to this procedure, we have already initialised all data	*)
    (* pertaining to the physical drives, but do not yet have a logical	*)
    (* drive structure.  This procedure reads the partition table on	*)
    (* physical block 0 of each physical drive, sets up the logical	*)
    (* drive information, and installs us as a device driver known to	*)
    (* module Devices.							*)

    VAR bufptr: POINTER TO ARRAY [0..BlockSize-1] OF BYTE;
	drive: PhysicalDriveNumber;  unit: CARDINAL;
	bootblockptr, sizeptr: POINTER TO LONGCARD;
	subscript: [0..BlockSize-1];
	Registers: RegisterPacket;
	(*# save, data(near_ptr=>off) *)
	StringAddress: POINTER TO ARRAY [0..63] OF CHAR;
	(*# restore *)

    BEGIN
	FOR unit := 0 TO MAX(LogicalDriveNumber) DO
	    WITH LogicalUnit[unit] DO
		physicaldrive := 0;  bootblock := 0;
		numberofsectors := 0;
	    END (*WITH*);
	END (*FOR*);
	unit := 0;
	ALLOCATE (bufptr, BlockSize);
	FOR drive := 0 TO NumberOfPhysicalDrives-1 DO
	    IF ReadPhysicalBlock (drive, 0, BlockSize, bufptr) = OK
	      THEN
		FOR subscript := 01C0H TO 01F0H BY 10H DO
		    IF bufptr^[subscript] <> BYTE(0) THEN
			bootblockptr := AddOffset (bufptr, subscript+6);
			sizeptr := AddOffset (bufptr, subscript+10);
			WITH LogicalUnit[unit] DO
			    physicaldrive := drive;
			    bootblock := bootblockptr^;
			    numberofsectors := sizeptr^;
			END (*WITH*);
			INC (unit);
		    END (*IF*);
		END (*FOR*);
	    END (*IF*);
	END (*FOR*);
	DEALLOCATE (bufptr, BlockSize);

	(* Having collected all necessary information from the	*)
	(* partition tables, set up the drive names.		*)

	NumberOfLogicalDrives := unit;
	IF NumberOfLogicalDrives > 0 THEN
	    harddisk := InstallDeviceDriver (NumberOfLogicalDrives);
	    FOR unit := 0 TO NumberOfLogicalDrives-1 DO

		(* Read the current directory for the this logical drive.	*)

		WITH Registers DO
		    DL := SHORTCARD(unit) + 3;
		    AH := 71;
		    BIOS (33, Registers);
		    StringAddress := MakePointer (DS, SI);
		END (*WITH*);

		(* Pass the information to module Devices.	*)

		DeviceName (harddisk, unit, CHR(ORD("C")+unit),
			LogicalUnit[unit].numberofsectors, StringAddress^);

	    END (*FOR*);
	END (*IF*);

    END DecodePartitionTable;

(************************************************************************)
(*				MAIN PROGRAM				*)
(************************************************************************)

VAR Mpage: MaintenancePage;

BEGIN
    IF testing THEN
	CreateMaintenancePage (Mpage);
	OpenWindow (log, black, green, 0,23, 0,60, simpleframe, doubledivider);
	Associate (log, Mpage);
	WriteString (log,"         Diagnostic output from hard disk driver");
	ChangeScrollingRegion (log, 3, 22);
    END (*IF*);
    CreateSemaphore (DataRequest, 0);
    NumberOfPhysicalDrives := 0;
    SetUpDrive (0, 104H);
    SetUpDrive (1, 118H);
    IF NumberOfPhysicalDrives = 0 THEN
	NumberOfLogicalDrives := 0;
    ELSE
	CreateInterruptTask (DiskInterrupt, DRQInterruptHandler,
						"Hard disk int");
	DecodePartitionTable;
    END (*IF*);
    IF NumberOfLogicalDrives > 0 THEN
	CreateTask (DiskRequestHandler, 13, "Harddisk queue");
    END (*IF*);
END HardDisk.