[Back to DRIVES SWAG index] [Back to Main SWAG index] [Original]

    {==========================================================================}
    { CDDrive - an interface to the CD-ROM device driver.                      }
    {--------------------------------------------------------------------------}
    { Copyright (c) 1996 C.J.Rankin   CJRankin@VossNet.Co.UK                   }
    {                                                                          }
    { This unit provides an elementary interface for controlling a CD-ROM      }
    { in TP6+. It has been left open-ended so that, if you wish, it can be     }
    { extended to provide a more comprehensive range of CD-ROM functions.      }
    {                                                                          }
    { Note that in its current form, it will *only* recognise the first CD-ROM }
    { in the system- not a problem for most of us.                             }
    {                                                                          }
    {--------------------------------------------------------------------------}
    {                                                                          }
    { Note: Windows 95 uses a protected mode CD-ROM driver and interface       }
    {       (MSCDEX v2.95). As a result, the standard way of requesting IOCTL  }
    {       functions of opening a file handle to the driver using Assign()    }
    {       and Reset() and then calling DOS services AX=$4402/$4403 DOES NOT  }
    {       WORK: DOS cannot open the CD-ROM driver in the DOS driver-list and }
    {       so opens a new file on the hard disc instead.                      }
    {                                                                          }
    {==========================================================================}
    {$A-,B-,D+,F-,G+,I-,L+,O+,R-,S-,V-,X+}
    unit CDDrive;

    interface

    const
      ex_CDROMUnknownUnit       =  1;
      ex_CDROMUnready           =  2;
      ex_CDROMUnknownCommand    =  3;
      ex_CDROMCRCError          =  4;
      ex_CDROMBadReqStrucLen    =  5;
      ex_CDROMBadSeek           =  6;
      ex_CDROMUnknownMedia      =  7;
      ex_CDROMUnknownSector     =  8;
      ex_CDROMReadError         = 11;
      ex_CDROMGeneralFailure    = 12;
      ex_CDROMMediaUnavailable  = 14;
      ex_CDROMInvalidDiscChange = 15;

    const
      MaxCDROM = 26;
    type
      TCDROMIndex  = 1..MaxCDROM;
      TCDROMLetter = 0..MaxCDROM-1;
      TCDROMNumber = 0..MaxCDROM;

    {                                                             }
    { These are the explicit CD-ROM services provided by the unit }
    {                                                             }
    procedure Eject;      (* Eject CD-ROM                                      *)
    procedure Close;      (* Close CD-ROM tray                                 *)
    procedure Lock;       (* Lock CD-ROM drive                                 *)
    procedure Unlock;     (* Unlock CD-ROM drive                               *)
    procedure Reset;      (* Reinitialise CD-ROM: i.e. as if disc was changed. *)
    function GetNumberOfCDROMDrives: TCDROMNumber;
    function GetCDROMVersion: word;

    {                                                                       }
    { Templates for CD-ROM service requests. To implement another device    }
    { or IOCTL request, create a descendant object with the requisite extra }
    { fields and pass it to the appropriate requestor function.             }
    {                                                                       }
    type
      PDeviceRequest = ^TDeviceRequest;
      TDeviceRequest = object
                         HeaderLength: byte;
                         SubUnit:      byte;
                         CommandCode:  byte;
                         Status:       word;
                         Reserved:     array[1..8] of byte;
                       end;

      PIOCTLRequest = ^TIOCTLRequest;
      TIOCTLRequest = object
                        SubFn: byte;
                      end;

      TRequestFunc = function(var Request: TDeviceRequest): word;

    {                                                                          }
    { This constitutes the interface to the driver, enabling further functions }
    { to be added if desired. The return value is the driver's Status word:    }
    { Status Word:   Bit 15: Error flag. If set then Bits 0-7 are error code   }
    {                Bit  8: Request done flag.                                }
    {                Bit  9: Device busy flag.                                 }
    {                                                                          }
    function DriverRequest_v210(var Request: TDeviceRequest): word;
    function DriverBasicRequest(var Request: TDeviceRequest): word;
    function IOCTLInput(var Request: TIOCTLRequest; ReqLen: word): word;
    function IOCTLOutput(var Request: TIOCTLRequest; ReqLen: word): word;

    {                                                                          }
    { Errors are returned in this variable: the values are explained above ... }
    {                                                                          }
    var CDROMError: byte;

    {                                                                          }
    { DriverRequest_v210 enables CD-ROM driver requests for MSCDEX v2.10+. For }
    { earlier versions of MSCDEX, DriverBasicRequest is used instead. The      }
    { appropriate function is assigned to the following procedural variable.   }
    {                                                                          }
    var DriverRequest: TRequestFunc;

    implementation
    uses DOS;

    type
      TDrvName = array[1..8] of char;

      PCDROMDriver = ^TCDROMDriver;
      TCDROMDriver = record
                       NextDriver:         PCDROMDriver;
                       DeviceAttr:         word;
                       StrategyEntryPoint: word;
                       INTEntryPoint:      word;
                       DeviceName:         TDrvName;
                       Reserved:           word;
                       DriveLetter:        TCDROMNumber;
                       Units:              byte
                     end;

      TCDROMDriveEntry = record
                           SubUnit:     byte;
                           CDROMDriver: PCDROMDriver
                         end;

    type
      PDeviceIOCTLRequest = ^TDeviceIOCTLRequest;
      TDeviceIOCTLRequest = object(TDeviceRequest)
                              Media:  byte;
                              BufPtr: pointer;
                              BufLen: byte;
                            end;

    type
      TCDROMLock = (CDROM_Unlocked, CDROM_Locked);

      PCDROMLockRequest = ^TCDROMLockRequest;
      TCDROMLockRequest = object(TIOCTLRequest)
                            LockStatus: TCDROMLock;
                          end;

    var Regs:                Registers;
    var NumberOfCDROMDrives: TCDROMNumber;
    var CDROMDriveLetter:    array[TCDROMIndex] of TCDROMLetter;

    var CDROMDriverStrategyEntryPoint: procedure;
    var CDROMDriverINTEntryPoint:      procedure;

    {                                                                         }
    { This is the interface to the CD-ROM driver. The assumption here is that }
    { we are only dealing with the first CD-ROM drive in the system- not a    }
    { problem to us mere mortals who only HAVE one CD-ROM...                  }
    {                                                                         }
    function DriverRequest_v210(var Request: TDeviceRequest): word;
    begin
      with Regs do
        begin
          ax := $1510;
          es := Seg(Request);
          bx := Ofs(Request);
          cx := CDROMDriveLetter[1]; (* Letter of CD-ROM drive: A=0,B=1,C=2... *)
          Intr($2f,Regs)
        end;
      with Request do
        begin
          if Status and (1 shl 15) <> 0 then (* Check the error flag...*)
            CDROMError := lo(Status)         (* ... return the error   *)
          else
            CDROMError := 0;                 (* ... return `no error'  *)
          DriverRequest_v210 := Status
        end
    end;

    {                                                                     }
    { This method of calling CD-ROM driver functions should work for all  }
    { versions of MSCDEX. Again, assume that there is only 1 CD-ROM ...   }
    {                                                                     }
    function DriverBasicRequest(var Request: TDeviceRequest): word;
    begin
      with Request do
        begin
          SubUnit := 0; (* Only 1 CD-ROM, so it must be driver sub-unit 0 *)
          asm
            LES BX, [BP+OFFSET Request]
          end;
          CDROMDriverStrategyEntryPoint;
          CDROMDriverINTEntryPoint;
          if Status and (1 shl 15) <> 0 then (* Check the error flag...*)
            CDROMError := lo(Status)         (* ... return the error   *)
          else
            CDROMError := 0;                 (* ... return `no error'  *)
          DriverBasicRequest := Status
        end
    end;

    {                                                                        }
    { The CDROM driver can be asked to do LOTS of things; the IOCTL requests }
    { are only a very small part. In theory you could descend other buffers  }
    { from TDeviceRequest, fill in the HeaderLength, CommandCode and any new }
    { fields and send them off to DriverRequest for execution...             }
    {                                                                        }
    function IOCTLOutput(var Request: TIOCTLRequest; ReqLen: word): word;
    var
      DeviceRequestHeader: TDeviceIOCTLRequest;
    begin                               (* Descendant of TDeviceRequest *)
      with DeviceRequestHeader do
        begin
          HeaderLength := SizeOf(DeviceRequestHeader);
          CommandCode := $0C;
          BufPtr := @Request.SubFn;  (* These fields added to TDeviceRequest *)
          BufLen := ReqLen           (* for IOCTL commands...                *)
        end;
      IOCTLOutput := DriverRequest(DeviceRequestHeader)
    end;

    function IOCTLInput(var Request: TIOCTLRequest; ReqLen: word): word;
    var
      DeviceRequestHeader: TDeviceIOCTLRequest;
    begin                               (* Descendant of TDeviceRequest *)
      with DeviceRequestHeader do
        begin
          HeaderLength := SizeOf(DeviceRequestHeader);
          CommandCode := $03;
          BufPtr := @Request.SubFn;  (* These fields added to TDeviceRequest *)
          BufLen := ReqLen           (* for IOCTL commands...                *)
        end;
      IOCTLInput := DriverRequest(DeviceRequestHeader)
    end;

    {                                                                          }
    { Yes, I COULD have just put NumberOfCDROMDrives in the interface section, }
    { except that this number is important and I don't want users `fiddling'   }
    { with it. :-)                                                             }
    {                                                                          }
    function GetNumberOfCDROMDrives: TCDROMNumber;
    begin
      GetNumberOfCDROMDrives := NumberOfCDROMDrives
    end;

    {                                                                     }
    { The mechanism used to perform device driver requests depends on the }
    { version of MSCDEX, so we need a method of finding this out.         }
    {                                                                     }
    function GetCDROMVersion: word;
    begin
      with Regs do
        begin
          bx := 0;
          ax := $150c;
          Intr($2f,Regs);
          GetCDROMVersion := bx  (* Hi byte = Major version number *)
        end                      (* Lo byte = Minor version number *)
    end;

    procedure Eject;
    var
      Request: TIOCTLRequest;
    begin
      if NumberOfCDROMDrives > 0 then
        with Request do
          begin
            SubFn := 00;   (* IOCTL command code for Eject... *)
            IOCTLOutput(Request,SizeOf(Request))
          end
    end;

    procedure Reset;
    var
      Request: TIOCTLRequest;
    begin
      if NumberOfCDROMDrives > 0 then
        with Request do
          begin
            SubFn := 02;   (* IOCTL command code to reset the CD-ROM drive *)
            IOCTLOutput(Request,SizeOf(Request))
          end
    end;

    procedure Close;
    var
      Request: TIOCTLRequest;
    begin
      if NumberOfCDROMDrives > 0 then
        with Request do
          begin
            SubFn := 05;   (* IOCTL command code to close the CD-ROM drive *)
            IOCTLOutput(Request,SizeOf(Request))
          end
    end;

    {
    { This routine seems to require a CD in the drive. Otherwise it returns  }
    { CDROMError = 2 (on my machine anyway...)                               }
    {                                                                        }
    procedure Lock;
    var
      Request: TCDROMLockRequest;
    begin
      if NumberOfCDROMDrives > 0 then
        with Request do
          begin
            SubFn := 01;   (* ... locking the CDROM... *)
            LockStatus := CDROM_Locked;
            IOCTLOutput(Request,SizeOf(Request))
          end
    end;

    procedure Unlock;
    var
      Request: TCDROMLockRequest;
    begin
      if NumberOfCDROMDrives > 0 then
        with Request do
          begin
            SubFn := 01;   (* ... and unlocking ... *)
            LockStatus := CDROM_Unlocked;
            IOCTLOutput(Request,SizeOf(Request))
          end
    end;

    {                                                                       }
    { Store the Strategy and Interrupt Entry Points for the CD-ROM device   }
    { driver... Again, assume that there is only 1 CD-ROM drive, and so     }
    { SubUnit will always = 0                                               }
    {                                                                       }
    procedure SetUpEntryPoints;
    var
      CDDriveList: array[TCDROMIndex] of TCDROMDriveEntry;
    begin
      with Regs do
        begin
          ax := $1501;
          es := Seg(CDDriveList);
          bx := Ofs(CDDriveList);
          Intr($2f,Regs)
        end;
      with CDDriveList[1] do
        begin
          @CDROMDriverStrategyEntryPoint :=
                      Ptr(Seg(CDROMDriver^),CDROMDriver^.StrategyEntryPoint);
          @CDROMDriverINTEntryPoint :=
                      Ptr(Seg(CDROMDriver^),CDROMDriver^.INTEntryPoint)

        end
    end;

    {                                                                          }
    { Initialisation code: neither the number of CD-ROM drives nor their drive }
    { letters will change during execution, so we shall identify the drives    }
    { NOW and not do it again.                                                 }
    {                                                                          }
    begin
    {                                         }
    { Get number of CD-ROMs in the system ... }
    {                                         }
      with Regs do
        begin
          ax := $1500;
          bx := 0;
          Intr($2f,Regs);
          NumberOfCDROMDrives := bl;
    {                                                                         }
    { Get the drive-letters for CD-ROMSs... There is an MSCDEX function to do }
    { this for v2.00+, viz AX=$150D INT $2F. However we are assuming only one }
    { CD-ROM and so the drive letter has already been determined.             }
    {                                                                         }
          if NumberOfCDROMDrives > 0 then
            begin
              CDROMDriveLetter[1] := cl;
    {                                                                 }
    { Determine the entry points for direct device-driver requests... }
    {                                                                 }
              SetUpEntryPoints;
    {                                                                 }
    { Determine which mechanism to use for CD-ROM driver requests ... }
    {                                                                 }
              if GetCDROMVersion < 2*256 + 10 then
                DriverRequest := DriverBasicRequest
              else
                DriverRequest := DriverRequest_v210
            end
        end
    end.

    { ----------------------   DEMO PROGRAM ----------------------- }

    {$A+,B-,D+,E-,F-,G+,I+,L+,N-,O-,R-,S-,V-,X+}
    {$M 16384,0,655360}
    program CDDemo;
    uses CDDrive;

    var
      Version: word;
    begin
      if GetNumberOfCDROMDrives = 0 then
        writeln( 'This machine has no CD-ROM drive.' )
      else
        begin
          Version := GetCDROMVersion;
          writeln( 'MSCDEX v', hi(Version), '.', lo(Version) );
          Lock;
          writeln( 'Lock: Error = ', CDROMError );
          Unlock;
          writeln( 'Unlock: Error = ', CDROMError );
          Eject;
          writeln( 'Eject: Error = ', CDROMError );
          Close;
          writeln( 'Close: Error = ', CDROMError );
          Reset;
          writeln( 'Reset: Error = ', CDROMError )
        end
    end.

[Back to DRIVES SWAG index] [Back to Main SWAG index] [Original]