MODULE TrackDiskSupport;

IMPORT E: Exec,
       S:SYSTEM,
       ES: ExecSupport,
       TD: TrackDisk;

CONST
  tdname = "trackdisk.device";

TYPE
  IntDataPtr = UNTRACED POINTER TO IntData;
  IntData =  STRUCT
               sigTask: E.TaskPtr;
               sig: INTEGER;
             END;

VAR
  DiskPort: ARRAY 4 OF E.MsgPortPtr;
  DiskReqBlock: ARRAY 4 OF TD.IOExtTDPtr;
  dev: INTEGER;
  OpenDrives: SET;

  IntReqBlock: ARRAY 4 OF TD.IOExtTDPtr;
  int: ARRAY 4 OF E.InterruptPtr;
  sigs: ARRAY 4 OF INTEGER;
  intData: ARRAY 4 OF IntData;


(*------  Open TrackDisk:  ------*)


PROCEDURE OpenTrackDisk*(device: INTEGER; AllowNon35: BOOLEAN): SHORTINT;

(* OpenTrackDisk can be called up to 4 times with 4 different device-      *)
(* numbers (0 to 3). Higher values are not allowed.                        *)
(* AllowNon35 specifies whether 5.25 Drives should be opened or not        *)
(**** How can a drive be allocated and removed from Devicelist etc?  *******)

VAR
  err: SHORTINT;

BEGIN
  IF device IN OpenDrives THEN RETURN 100 END;
  NEW(int[device]);
  IF int[device]=NIL THEN RETURN 101 END;
  DiskPort[device] := ES.CreatePort("",0);
  IF DiskPort[device]=NIL THEN RETURN 101 END;
  DiskReqBlock[device] := ES.CreateExtIO(DiskPort[device],SIZE(DiskReqBlock[device]^));
  IntReqBlock [device] := ES.CreateExtIO(DiskPort[device],SIZE(IntReqBlock [device]^));
  IF DiskReqBlock[device]=NIL THEN RETURN 102 END;
  IF AllowNon35 THEN
    IF E.OpenDevice(tdname,device,DiskReqBlock[device],LONGSET{TD.allowNon35})#0 THEN END;
  ELSE
    IF E.OpenDevice(tdname,device,DiskReqBlock[device],LONGSET{})#0 THEN END;
  END;
  err := DiskReqBlock[device]^.req.error;
  IF err#0 THEN
    ES.DeleteExtIO(DiskReqBlock[device]);
    DiskReqBlock[device] := NIL;
    ES.DeletePort(DiskPort[device]);
    DiskPort[device] := NIL;
    DISPOSE(int[device]);
  ELSE
    IntReqBlock[device]^ := DiskReqBlock[device]^;
    INCL(OpenDrives,device);
  END;
  RETURN err;
END OpenTrackDisk;


(*------  Close TrackDisk:  ------*)


PROCEDURE CloseTrackDisk*(device: INTEGER);

BEGIN
  IF device IN OpenDrives THEN
     EXCL(OpenDrives,device);
     E.CloseDevice(DiskReqBlock[device]);
     ES.DeleteExtIO(DiskReqBlock[device]);
     DiskReqBlock[device] := NIL;
     ES.DeletePort(DiskPort[device]);
     DiskPort[device] := NIL;
     DISPOSE(int[device]);
  END;
END CloseTrackDisk;


(*------  Get Disk Change Value:  ------*)


PROCEDURE GetDiskChange*(device: INTEGER): LONGINT;

BEGIN
  DiskReqBlock[device]^.req.command := TD.changeNum;
  E.OldDoIO(DiskReqBlock[device]);
  RETURN DiskReqBlock[device]^.req.actual;
END GetDiskChange;


(*------  Is there a disk inserted?  ------*)


PROCEDURE ChangeState*(device: INTEGER): BOOLEAN;
(* returns TRUE if disk in drive *)

BEGIN
  DiskReqBlock[device]^.req.command := TD.changeState;
  E.OldDoIO(DiskReqBlock[device]);
  RETURN 0=DiskReqBlock[device]^.req.actual;
END ChangeState;


(*------  Test Write-Protection:  ------*)


PROCEDURE ProtStatus*(device: INTEGER): BOOLEAN;
(* TRUE if disk is Write enabled *)

BEGIN
  DiskReqBlock[device]^.req.command := TD.protStatus;
  E.OldDoIO(DiskReqBlock[device]);
  RETURN 0=DiskReqBlock[device]^.req.actual;
END ProtStatus;


(*------  Motor Control:  ------*)


PROCEDURE Motor*(device: INTEGER; On: BOOLEAN): BOOLEAN;
(* Turns DiskTD.motor on or off according to On. Result is old TD.motor state:   *)
(* TRUE = on, FALSE = off. *)

BEGIN
  IF On THEN
    DiskReqBlock[device]^.req.length := 1;
  ELSE
    DiskReqBlock[device]^.req.length := 0;
  END;
  DiskReqBlock[device]^.req.command := TD.motor;
  E.OldDoIO(DiskReqBlock[device]);
  RETURN DiskReqBlock[device]^.req.actual=1;
END Motor;


(*------  Seek:  ------*)


PROCEDURE Seek*(device: INTEGER; Offset: LONGINT): SHORTINT;
(* Return error code *)

BEGIN
  DiskReqBlock[device]^.req.offset := Offset;
  DiskReqBlock[device]^.req.command:= TD.seek;
  RETURN E.DoIO(DiskReqBlock[device]);
END Seek;


(*------  Read Block:  ------*)


PROCEDURE ReadBlock*(device: INTEGER;
                    Block: INTEGER;
                    Buffer: E.ADDRESS;
                    BlockCnt: INTEGER;
                    ChangeCnt: LONGINT): SHORTINT;

(* Block:        the disk's block # to be read                             *)
(* Buffer:       at least 512 Bytes of Chip memory to store data           *)
(* BlockCnt:     Number of Blocks to be read                               *)
(* ChangeCnt:    last changenumber to check diskchanges                    *)
(* result:       error number                                              *)


BEGIN
    DiskReqBlock[device]^.req.length := LONG(BlockCnt)*TD.sector;
    DiskReqBlock[device]^.req.data   := Buffer;
    DiskReqBlock[device]^.req.command:= TD.extRead;
    DiskReqBlock[device]^.count      := ChangeCnt;
    DiskReqBlock[device]^.req.offset := TD.sector*LONG(Block);
  RETURN E.DoIO(DiskReqBlock[device]);
END ReadBlock;


(*------  Read Sector:  ------*)


PROCEDURE ReadCycSec*(device: INTEGER;
                     cyl,sec,head: INTEGER;
                     Buffer: E.ADDRESS;
                     BlockCnt: INTEGER;
                     ChangeCnt: LONGINT): SHORTINT;

(* cyl,sec,head: specify the block to be read                              *)
(* Buffer:       512 bytes of Chip memory to store data                    *)
(* BlockCnt:     Number of Blocks to be read                               *)
(* ChangeCnt:    last changenumber to check diskchanges                    *)
(* result:       error number                                              *)


BEGIN
  RETURN ReadBlock(device,sec+TD.numSecs*(head+2*cyl),Buffer,BlockCnt,ChangeCnt);
END ReadCycSec;


(*------  Write Block:  ------*)


PROCEDURE WriteBlock*(device: INTEGER;
                     block: INTEGER;
                     Buffer: E.ADDRESS;
                     BlockCnt: INTEGER;
                     ChangeCnt: LONGINT): SHORTINT;

(* block:        the block # to be written                                 *)
(* Buffer:       512 INTEGERs of Chip memory storing the data                 *)
(* BlockCnt:     # of blocks to be written                                 *)
(* ChangeCnt:    last changenumber to check diskchanges                    *)
(* result:       error number                                              *)

BEGIN
    DiskReqBlock[device]^.req.length := LONG(BlockCnt)*TD.sector;
    DiskReqBlock[device]^.req.data   := Buffer;
    DiskReqBlock[device]^.req.command:= TD.extWrite;
    DiskReqBlock[device]^.count      := ChangeCnt;
    DiskReqBlock[device]^.req.offset := TD.sector*LONG(block);
    RETURN E.DoIO(DiskReqBlock[device]);
END WriteBlock;


(*------  Write Sector:  ------*)


PROCEDURE WriteCycSec*(device: INTEGER;
                      cyl,sec,head: INTEGER;
                      Buffer: E.ADDRESS;
                      BlockCnt: INTEGER;
                      ChangeCnt: LONGINT): SHORTINT;

(* cyl,sec,head: specify the block to be written                           *)
(* Buffer:       512 bytes of Chip memory storing the data                 *)
(* BlockCnt:     # of blocks to be written                                 *)
(* ChangeCnt:    last changenumber to check diskchanges                    *)
(* result:       error number                                              *)


BEGIN
  RETURN WriteBlock(device,sec+TD.numSecs*(head+2*cyl),Buffer,BlockCnt,ChangeCnt);
END WriteCycSec;


(*------  Update:  ------*)


PROCEDURE Update*(device: INTEGER;
                 ChangeCnt: LONGINT): SHORTINT;

BEGIN
    DiskReqBlock[device]^.req.command:= TD.extUpdate;
    DiskReqBlock[device]^.count      := ChangeCnt;
    RETURN E.DoIO(DiskReqBlock[device]);
END Update;


(*------  Format:  ------*)


PROCEDURE FormatTrack*(device: INTEGER;
                 cyl: INTEGER;
                 Buffer: E.ADDRESS;
                 CycCnt: INTEGER;
                 ChangeCnt: LONGINT): SHORTINT;

(* cyl:       First Cyclinder to be formatted                              *)
(* Buffer:    Buffer containing cyclinder data (at least 22*512 bytes)     *)
(* CycCnt:    Number of Tracks to be formatted                             *)
(* ChangeCnt: last changenumber to check diskchanges                       *)
(* result:    error number                                                 *)


BEGIN
    DiskReqBlock[device]^.req.length := LONG(CycCnt)*TD.sector*TD.numSecs*2;
    DiskReqBlock[device]^.req.data   := Buffer;
    DiskReqBlock[device]^.req.command:= TD.extFormat;
    DiskReqBlock[device]^.count      := ChangeCnt;
    DiskReqBlock[device]^.req.offset := TD.sector*TD.numSecs*2*LONG(cyl);
    RETURN E.DoIO(DiskReqBlock[device]);
END FormatTrack;


(*------  Create DiskTD.remove-Interrupt:  ------*)


PROCEDURE Remove*(device: INTEGER;
                 IntProc: E.ADDRESS): SHORTINT;

(* IntProc: The Interrupt Procedure's E.ADDRESS or NIL to TD.remove it          *)

BEGIN
    DiskReqBlock[device]^.req.data   := IntProc;
    DiskReqBlock[device]^.req.command:= TD.remove;
    RETURN E.DoIO(DiskReqBlock[device]);
END Remove;


(*------  Mark track buffer as invalid:  ------*)


PROCEDURE Clear*(device: INTEGER);

BEGIN
  DiskReqBlock[device]^.req.command := E.clear;
  E.OldDoIO(DiskReqBlock[device]);
END Clear;


(*------  3.5 or 5.25 Drive:  ------*)


PROCEDURE GetDriveType*(device: INTEGER): LONGINT;

(* Returns 0 if 3.5 inch drive connected, else 1 (5.25")                   *)

BEGIN
  DiskReqBlock[device]^.req.command := TD.getDriveType;
  E.OldDoIO(DiskReqBlock[device]);
  RETURN DiskReqBlock[device]^.req.actual;
END GetDriveType;


(*------  How many Trax:  ------*)


PROCEDURE GetNumTracks*(device: INTEGER): LONGINT;

BEGIN
  DiskReqBlock[device]^.req.command := TD.getNumTracks;
  E.OldDoIO(DiskReqBlock[device]);
  RETURN DiskReqBlock[device]^.req.actual;
END GetNumTracks;


(*------  IntProc:  ------*)


PROCEDURE IntProc(data{9}: IntDataPtr); (* $SaveRegs+ $StackChk- $Debug- *)

BEGIN
  E.Signal(data.sigTask,LONGSET{data.sig});
  IF data=data THEN END;        (* Set Z flag *)
END IntProc; (* $StackChk= $Debug= *)


(*------  AddChangeInt:  ------*)


PROCEDURE AddChangeInt*(device: INTEGER): INTEGER;
(*
 * Add Change interrupt. Returns signal to wait for or -1.
 *
 * Do not forget to call RemChangeInt if you have called this function and it
 * succeeded.
 *)

BEGIN
  IntReqBlock[device]^.req.command := TD.addChangeInt;
  IntReqBlock[device]^.req.flags   := SHORTSET{};
  IntReqBlock[device]^.req.length  := SIZE(int[device]^);
  IntReqBlock[device]^.req.data    := int[device];
  int[device].node.type := E.interrupt;
  int[device].node.pri  := 0;
  int[device].node.name := NIL;
  int[device].data := S.ADR(intData[device]);
  int[device].code := S.VAL(E.PROC,IntProc);
  intData[device].sig := E.AllocSignal(-1);
  IF intData[device].sig<0 THEN RETURN -1 END;
  intData[device].sigTask := E.FindTask(NIL);
  E.SendIO(IntReqBlock[device]);
  RETURN intData[device].sig;
END AddChangeInt;


(*------  RemChangeInt:  ------*)


PROCEDURE RemChangeInt*(device: INTEGER): INTEGER;
(*
 * Remove Change interrupt. Returns Dos error code.
 *
 * Do not call this if you haven't called AddChangeInt!
 * Do not call this if you have called AddChangeInt and it failed!
 *)

VAR
  success: INTEGER;

BEGIN
  IntReqBlock[device]^.req.command  := TD.remChangeInt;
  DiskReqBlock[device]^.req.flags   := SHORTSET{};
  DiskReqBlock[device]^.req.length  := SIZE(int[device]^);
  DiskReqBlock[device]^.req.data    := int[device];
  success := E.DoIO(IntReqBlock[device]);
  IF success=0 THEN
    E.FreeSignal(intData[device].sig);
  END;
  RETURN success;
END RemChangeInt;


(*------  Initialization:  ------*)


BEGIN
  FOR dev := 0 TO 3 DO
    DiskReqBlock[dev] := NIL;
    DiskPort[dev] := NIL;
  END;
  OpenDrives := {};
  
CLOSE

  FOR dev:=0 TO 3 DO
    IF dev IN OpenDrives     THEN CloseTrackDisk(dev) END;
    IF DiskReqBlock[dev]#NIL THEN ES.DeleteExtIO(DiskReqBlock[dev]) END;
    IF DiskPort[dev]    #NIL THEN ES.DeletePort(DiskPort[dev]) END;
  END;

END TrackDiskSupport.
