/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT (C) Microsoft Corporation, 1989                                 */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = VCDROM2F.C
 *
 * DESCRIPTIVE NAME = VCDROM MSCDEX 2F support for CD-ROM in a DOS session.
 *
 * DESCRIPTION This module contains the (MSCDEX) int 2fh and DOS CD-ROM
 *             device driver service routines.
 *
 *
 * ENTRY POINTS:
 *             VCDROMDosLink
 *               SendDeviceRequest
 *               ResetMedia
 *               GetDriveHandle
 *               MapOS2toDosCode
 *               GetPlayStatus
 *               HsgToRedbook
 *               AddSectors
 *
 * EXTERNAL REFERENCES:
 *
*/


#define INCL_DOSERRORS
#include <bseerr.h>
#include <mvdm.h>
#include <mscdex.h>
#include "vcdromp.h"
#include "reqhdr.h"
#include "ctrlblk.h"


#ifdef  VDDSTRICT
MODNAME = __FILE__;
#endif

/*
**  External References
*/

/*
**  Global Data
*/
extern
BOOL    flSLAVE;
extern
PFN     pfnSlaveRequest;

extern
struct
CategoryCode
IoctlInputCodes [];

extern
struct
CategoryCode
IoctlOutputCodes [];

extern
USHORT  NumberOfDrives;
extern
USHORT  FirstDriveNumber;

extern
HFILE
DriveHandle [MAX_NUMBER_OF_DRIVES];         /* DASD-open drive handles      */

extern
UCHAR
DriveString [];                             /* Drive letter strings         */

extern
VPDOSDDTYPE DosDDHeader;                    /* V86FAR address of dos driver */


/*
**  Instance Data
*/
extern
HVDM        hvdmCurrent;
extern
ULONG       slaveparms [];
extern
ULONG       DataArea[];
extern
ULONG       ParmList[];
extern
ULONG       DataLengthInOut;
extern
ULONG       ParmLengthInOut;
extern
HFILE       FileHandle;                     /* for VDHOpen                  */
extern
ULONG       ActionTaken;                    /* for VDHOpen                  */


#pragma BEGIN_GLOBAL_CODE

#pragma END_GLOBAL_CODE

#pragma BEGIN_SWAP_CODE


/****************************************************************************
 *
 * FUNCTION NAME = VCDROMDosLink
 *
 * DESCRIPTION   = Handle MSCDEX 2f interrupts ( AH == 15h )
 *
 *       The DOS stub device driver hooks the MSCDEX 2f interrupts and
 *       calls this function by use of an ARPL instruction.
 *
 *       Some MSCDEX (DOS CD-ROM file system driver) 2f services are
 *       implemented here others by the OS/2 CD-ROM installable file
 *       system (CDROM.IFS).  VCDROM calls the installable file system
 *       via the VDHFSCtl service.
 *
 *       MSCDEX API parameters must be real mode addresses (per the
 *       MSCDEX 2.21 Specification).
 *
 *       Audio and other ioctls are supported through Send Driver Request.
 *       The app formats an ioctl input or ioctl output DOS driver request
 *       packet to be forwarded to the CD-ROM device driver for service.
 *       The format of these requests is described in the Microsoft
 *       MSCDEX spec. The method used here is to convert the DOS request
 *       into an OS/2 generic ioctl that is sent to the OS/2 CD-ROM device
 *       driver through the VDHDevIOCtl call. The format of the OS/2 CD-ROM
 *       ioctls is described in the CDFS (OS/2 file system driver) spec.
 *
 *       Some DOS apps (Groliers) bypass MSCDEX, calling the dos driver
 *       directly. The DOS stub converts these requests into 2f Send Driver
 *       Request format for consistency.
 *
 *       CONTEXT:
 *          VDM Task-time
 *
 *       PSEUDOCODE:
 *          VDHPopInt to prevent ROM code execution.
 *          If MSCDEX function 10h, call SendDeviceRequest,
 *           otherwise either call the installable file system for service
 *           or service it here.
 *
 * INPUT         = pcrf   - pointer to client register frame
 *
 * OUTPUT        = returns TRUE == serviced interrupt
 *                 returns FALSE == not mine
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

BOOL HOOKENTRY VCDROMDosLink (PVOID pHookData, register PCRF pcrf)
{
    register
    struct
    DosRh   *reqhdr;                      /* dos request header              */
    register
    PUCHAR  buffer;
    PUCHAR  szpathname;                   /* ptr to path name                */
    USHORT  i;                            /* counter                         */
    PUCHAR  ptmp1;                        /* temp ptr                        */
    ULONG   drive_number;
    ULONG   n;
    ULONG   rc;
    PULONG  vdd_name;
    HFILE   handle;
    ULONG   sector_index;

/* INT_3; */
    VDHPopInt();                          /* to prevent ROM code execution   */

    if ( AL (pcrf) == SEND_DEVICE_REQUEST )
    {
        reqhdr = PFROMVADDR ( ES (pcrf), BX (pcrf) );
        drive_number = CX (pcrf);
        if ( drive_number == 0 )               /* 0 = no drive number        */
        {
            drive_number = UNIT_TO_DRIVE_NUMBER ( reqhdr->subunit );
        }
        SendDeviceRequest ( reqhdr, drive_number );

    }
    else                                       /* infrequent 2f services     */
    {
        switch ( AL (pcrf) )
        {
            case GET_NUMBER_CDROM_DRIVE_LETTERS:

                BX (pcrf) = NumberOfDrives;
                CX (pcrf) = FirstDriveNumber;
                break;

            case GET_CDROM_DRIVE_DEVICE_LIST:

                buffer = PFROMVADDR ( ES (pcrf), BX (pcrf) );
                for ( n = 0; n < NumberOfDrives; ++n )
                {
                    *buffer = (UCHAR) n;       /* subunit number             */
                    ++buffer;
                    * ((PULONG) buffer) = (ULONG) DosDDHeader;
                    buffer += 4;
                }
                break;

            case GET_COPYRIGHT_FILE_NAME:
            case GET_ABSTRACT_FILE_NAME:
            case GET_BIBLIOGRAPHIC_FILE_NAME:

               /*
               ** get VDHOpen handle to CD-ROM drive specified in CX
               */
               drive_number = CX (pcrf);
               handle = GetDriveHandle (drive_number);
               if (handle == NULL)
               {
                  AX (pcrf) = ERROR_I24_WRONG_DISK;      // INVALID_DRIVE
                  FL (pcrf) |= CARRY_FLAG;
                  break;
               }

               /*
               ** convert virtual address to 0:32 flat address
               */
               buffer = (PUCHAR)((ULONG)PFROMVADDR(ES(pcrf),BX(pcrf))+
                                                  (ULONG)hvdmCurrent);

                                                /* set user buffer length    */
               DataLengthInOut=MAX_FILE_ID_LEN;
                                                /* set MSCDEX function code  */
               ((PGETFILE)ParmList)->ulFunction=(ULONG)AL(pcrf);
                                                /* set structure length      */
               ParmLengthInOut=sizeof(GETFILE);

               /*
               ** call CD-ROM IFS for file name
               */
               rc = VDHFSCtl((PVOID)buffer,
                             (ULONG)DataLengthInOut,
                             (PVOID)&DataLengthInOut,
                             (PVOID)&ParmList,
                             (ULONG)ParmLengthInOut,
                             (PVOID)&ParmLengthInOut,
                             (ULONG)FSCTL_ARG_MSCDEX,
                             (PSZ)NULL,
                             handle,
                             FSCTL_HANDLE);

               if ( rc == VDH_FAILURE )
               {
                   rc = VDHGetError ();
                   AX (pcrf) = ERROR_I24_NOT_READY;      // DRIVE_NOT_READY
                   FL (pcrf) |= CARRY_FLAG;
                   break;
               }
               FL (pcrf) &= ~CARRY_FLAG;
               break;

            case READ_VTOC:

               /*
               ** get VDHOpen handle to CD-ROM drive specified in CX
               */
               drive_number = CX (pcrf);
               handle = GetDriveHandle (drive_number);
               if (handle == NULL)
               {
                  AX (pcrf) = ERROR_I24_WRONG_DISK;      // INVALID_DRIVE
                  FL (pcrf) |= CARRY_FLAG;
                  break;
               }

               /*
               ** convert virtual address to 0:32 flat address
               */
               buffer = (PUCHAR)((ULONG)PFROMVADDR(ES(pcrf),BX(pcrf))+
                                                  (ULONG)hvdmCurrent);

                                                /* set user buffer length    */
               DataLengthInOut=CDROM_SECTOR_SIZE;
                                                /* set MSCDEX function code  */
               ((PREADVTOC)ParmList)->ulFunction=(ULONG)AL(pcrf);
                                                /* set MSCDEX sector index   */
               ((PREADVTOC)ParmList)->ulSectorIndex=(ULONG)DX(pcrf);
                                                /* set structure length      */
               ParmLengthInOut=sizeof(READVTOC);

               /*
               ** call CD-ROM IFS to read VTOC
               */
               rc = VDHFSCtl((PVOID)buffer,
                             (ULONG)DataLengthInOut,
                             (PVOID)&DataLengthInOut,
                             (PVOID)&ParmList,
                             (ULONG)ParmLengthInOut,
                             (PVOID)&ParmLengthInOut,
                             (ULONG)FSCTL_ARG_MSCDEX,
                             (PSZ)NULL,
                             handle,
                             FSCTL_HANDLE);

               if ( rc == VDH_FAILURE )
               {
                   rc = VDHGetError ();
                   AX (pcrf) = ERROR_I24_NOT_READY;      // DRIVE_NOT_READY
                   FL (pcrf) |= CARRY_FLAG;
                   break;
               }
               else
               {
                   if (*(PUCHAR)ParmList == STANDARD_VOLUME_DESCRIPTOR)
                        AX (pcrf) = STANDARD_VOLUME_DESCRIPTOR;
                   else
                   {
                        if (*(PUCHAR)ParmList == VOLUME_DESCRIPTOR_TERMINATOR)
                             AX (pcrf) = VOLUME_DESCRIPTOR_TERMINATOR;
                        else
                             AX (pcrf) = OTHER_VOLUME_DESCRIPTOR;
                   }
                   FL (pcrf) &= ~CARRY_FLAG;
               }
               break;

            case ABSOLUTE_DISK_READ:

               /*
               ** get VDHOpen handle to CD-ROM drive specified in CX
               */
               drive_number = CX (pcrf);
               handle = GetDriveHandle (drive_number);
               if (handle == NULL)
               {
                  AX (pcrf) = ERROR_I24_WRONG_DISK;      // INVALID_DRIVE
                  FL (pcrf) |= CARRY_FLAG;
                  break;
               }

               /*
               ** convert virtual address to 0:32 flat address
               */
               buffer = (PUCHAR)((ULONG)PFROMVADDR(ES(pcrf),BX(pcrf))+
                                                  (ULONG)hvdmCurrent);

                                                /* set user buffer length    */
               DataLengthInOut=CDROM_SECTOR_SIZE * DX(pcrf);
                                                /* set MSCDEX function code  */
               ((PABSREAD)ParmList)->ulFunction=(ULONG)AL(pcrf);
                                                /* set MSCDEX sector count   */
               ((PABSREAD)ParmList)->ulSectorCount=(ULONG)DX(pcrf);
                                                /* set MSCDEX sector start   */
               ((PABSREAD)ParmList)->usSectStartLo=DI(pcrf);
                                                /* set MSCDEX sector start   */
               ((PABSREAD)ParmList)->usSectStartHi=SI(pcrf);
                                                /* set structure length      */
               ParmLengthInOut=sizeof(ABSREAD);

               if (flSLAVE)                     /* if INT_DURING_IO property */
               {
                 slaveparms[0] = (ULONG)buffer;
                 slaveparms[1] = DataLengthInOut;
                 slaveparms[2] = (ULONG)&DataLengthInOut;
                 slaveparms[3] = (ULONG)&ParmList;
                 slaveparms[4] = ParmLengthInOut;
                 slaveparms[5] = (ULONG)&ParmLengthInOut;
                 slaveparms[6] = FSCTL_ARG_MSCDEX;
                 slaveparms[7] = NULL;
                 slaveparms[8] = (ULONG)handle;
                 slaveparms[9] = FSCTL_HANDLE;

                 pcrf->crf_eax = (ULONG)&slaveparms[0]; /* pass parameters   */
                 pfnSlaveRequest (pcrf);        /* call slave thread         */
               }
               else
               {
                 /*
                 ** call CD-ROM IFS for ABSOLUTE DISK READ
                 */
                 rc = VDHFSCtl((PVOID)buffer,
                               (ULONG)DataLengthInOut,
                               (PVOID)&DataLengthInOut,
                               (PVOID)&ParmList,
                               (ULONG)ParmLengthInOut,
                               (PVOID)&ParmLengthInOut,
                               (ULONG)FSCTL_ARG_MSCDEX,
                               (PSZ)NULL,
                               handle,
                               FSCTL_HANDLE);

                 if ( rc == VDH_FAILURE )
                 {
                     rc = VDHGetError ();
                     AX (pcrf) = ERROR_I24_NOT_READY;    // DRIVE_NOT_READY
                     FL (pcrf) |= CARRY_FLAG;
                     break;
                 }
               } /* endif */

               /* successful return. */
               AX (pcrf) = NO_ERROR;
               FL (pcrf) &= ~CARRY_FLAG;
               break;

            case ABSOLUTE_DISK_WRITE:
                break;

            case CDROM_DRIVE_CHECK:

                BX (pcrf) = MSCDEX_SIGNATURE;
                drive_number = CX (pcrf);
                if ( drive_number < FirstDriveNumber  ||
                     drive_number >= FirstDriveNumber + NumberOfDrives )
                {
                    AX (pcrf) = 0;
                }
                break;

            case GET_MSCDEX_VERSION:

                BX (pcrf) = MSCDEX_VERSION;
                break;

            case GET_CDROM_DRIVE_LETTERS:

                buffer = PFROMVADDR ( ES (pcrf), BX (pcrf) );
                for ( n = 0; n < NumberOfDrives; ++n )
                {
                    *buffer = (UCHAR) ( FirstDriveNumber + n );
                    ++buffer;
                }
                break;

            case VOLUME_DESCRIPTOR_PREFERENCE:

               /*
               ** get VDHOpen handle to CD-ROM drive specified in CX
               */
               drive_number = CX (pcrf);
               handle = GetDriveHandle (drive_number);
               if (handle == NULL)
               {
                  AX (pcrf) = ERROR_I24_WRONG_DISK;      // INVALID_DRIVE
                  FL (pcrf) |= CARRY_FLAG;
                  break;
               }
                                         /* set MSCDEX function code         */
               ((PVOLDESCPREF)ParmList)->ulFunction=(ULONG)AL(pcrf);
                                         /* set MSCDEX get/set pref request  */
               ((PVOLDESCPREF)ParmList)->ulPrefReq=(ULONG)BX(pcrf);
                                         /* set MSCDEX get/set pref value    */
               ((PVOLDESCPREF)ParmList)->ulPrefVal=(ULONG)DX(pcrf);
                                         /* set structure length             */
               ParmLengthInOut=sizeof(VOLDESCPREF);
                                         /* set user buffer length           */
               DataLengthInOut=0;

               /*
               ** call CD-ROM IFS for GET DIRECTORY ENTRY
               */
               rc = VDHFSCtl((PVOID)buffer,
                             (ULONG)DataLengthInOut,
                             (PVOID)&DataLengthInOut,
                             (PVOID)&ParmList,
                             (ULONG)ParmLengthInOut,
                             (PVOID)&ParmLengthInOut,
                             (ULONG)FSCTL_ARG_MSCDEX,
                             (PSZ)NULL,
                             handle,
                             FSCTL_HANDLE);

               if ( rc == VDH_FAILURE )
               {
                   rc = VDHGetError ();
                   AX (pcrf) = (USHORT) rc;
                   FL (pcrf) |= CARRY_FLAG;
                   break;
               }

               /*
               ** return volume descriptor preference
               */
               if (BX(pcrf) == GET_VOL_DESC_PREF)
                   DX(pcrf) = (USHORT)((PVOLDESCPREF)ParmList)->ulPrefVal;

               FL (pcrf) &= ~CARRY_FLAG;
               break;

            case GET_DIRECTORY_ENTRY:

               /*
               ** get VDHOpen handle to CD-ROM drive specified in CH
               */
               drive_number = CL (pcrf);
               handle = GetDriveHandle (drive_number);
               if (handle == NULL)
               {
                  AX (pcrf) = ERROR_I24_WRONG_DISK;      // INVALID_DRIVE
                  FL (pcrf) |= CARRY_FLAG;
                  break;
               }

               /*
               ** Directory Entry buffer
               ** convert virtual address to 0:32 flat address
               */
               buffer = (PUCHAR)((ULONG)PFROMVADDR(SI(pcrf),DI(pcrf))+
                                                  (ULONG)hvdmCurrent);

               if (CH(pcrf) == 0)                  /* set user buffer length */
                  DataLengthInOut=DIR_ENTRY_SPECIFIC_LEN;
               else                                /* set user buffer length */
                  DataLengthInOut=DIR_ENTRY_GENERAL_LEN;

               /*
               ** ASCIIZ filename buffer
               ** convert virtual address to 0:32 flat address
               */
               szpathname = PFROMVADDR(ES(pcrf),BX(pcrf));

                                                 /* set MSCDEX function code */
               ((PGETDIRENTRY)ParmList)->ulFunction=(ULONG)AL(pcrf);
                                                 /* set MSCDEX copy flags    */
               ((PGETDIRENTRY)ParmList)->ulFlags=(ULONG)CH(pcrf);

               i=0;
               ptmp1=(PUCHAR)ParmList;           /* get ptr to variable part */
               ptmp1+=sizeof(GETDIRENTRY);
               do
               {
                 *ptmp1=*szpathname;
                 ptmp1++;
                 szpathname++;
                 i++;
               } while (*(szpathname-1) != NULL); /* enddo */
                                                 /* set structure length     */
               ParmLengthInOut=(ULONG)(i+sizeof(GETDIRENTRY));

               /*
               ** call CD-ROM IFS for GET DIRECTORY ENTRY
               */
               rc = VDHFSCtl((PVOID)buffer,
                             (ULONG)DataLengthInOut,
                             (PVOID)&DataLengthInOut,
                             (PVOID)&ParmList,
                             (ULONG)ParmLengthInOut,
                             (PVOID)&ParmLengthInOut,
                             (ULONG)FSCTL_ARG_MSCDEX,
                             (PSZ)NULL,
                             handle,
                             FSCTL_HANDLE);

               if ( rc == VDH_FAILURE )
               {
                   rc = VDHGetError ();
                   AX (pcrf) = (USHORT) rc;
                   FL (pcrf) |= CARRY_FLAG;
                   break;
               } else
                   /* High Sierra = 0, ISO-9660 = 1 */
                   AX (pcrf) = (USHORT)((PGETDIRENTRY)ParmList)->ulFlags;

               FL (pcrf) &= ~CARRY_FLAG;
               break;

            default:

                AX (pcrf) = ERROR_INVALID_2F_FUNCTION;
                FL (pcrf) |= CARRY_FLAG;
        }
    }

    return TRUE;                                  /* claimed the interrupt   */
}


/****************************************************************************
 *
 * FUNCTION NAME = SendDeviceRequest
 *
 * DESCRIPTION   = SendDeviceRequest to the OS/2 driver
 *
 *       This function converts the supported DOS driver request packets
 *       to VDHDevIOCtl calls and directs them to the OS/2 CD-ROM device
 *       driver.
 *
 *       COMPARISON OF PARAM/DATA AREAS
 *
 *            DOS PARAMS       DOS DATA           OS/2 PARAMS
 *           ------------     ------------      --------------
 *                                                   C
 *                                                   D
 *                                                   0
 *           command code                            1
 *           param byte 1      data byte 1       param byte 1
 *           param byte 2      data byte 1       param byte 2
 *                 .                .                .
 *                 .                .                .
 *                 .                .                .
 *
 *           The parameters generally line up, with some exceptions. Data
 *           overlaps parameters in DOS, not in OS/2.
 *
 *       CONTEXT:
 *           VDM Task-time
 *
 *       PSEUDOCODE:
 *           Verify the drive number.
 *           Get the drive handle.
 *           Map the 2f call to VDHDevIOCtl and call it.
 *           Map the os/2 return code to a dos rc.
 *           Reset media if necessary, otherwise
 *            check for drive playing and update the rc.
 *
 * INPUT         = reqhdr - flat address of dos request packet
 *                 drive_number - of the CD-ROM (0=A, 1=B...)
 *
 * OUTPUT        = void
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

VOID PRIVENTRY SendDeviceRequest ( register
                                   struct
                                   DosRh   *reqhdr,         /* flat address  */
                                   ULONG    drive_number )
{
    register
    PCHAR       control_block;
    register
    ULONG       rc;
    ULONG       saveCD01area;
    ULONG       save_number_of_sectors;
    ULONG       save_volume_size;
    F16PVOID    save_transfer_area;
    HFILE       handle;
    struct
    CategoryCode ioctl;
    PVOID       parm_list;
    PVOID       data_area;
    BOOL        media_check_in_progress;

    if ( drive_number < FirstDriveNumber  ||
         drive_number >= FirstDriveNumber + NumberOfDrives )
    {
        reqhdr->status = ERROR + DONE + ERROR_I24_BAD_UNIT;
        return;
    }

    handle = GetDriveHandle ( drive_number );
    if ( handle == NULL )
    {
        reqhdr->status = ERROR + DONE + ERROR_I24_NOT_READY;
        return;
    }                                         /* error exit                  */

    ParmLengthInOut = PARM_LENGTH_MAX;        /* these are just dummies for  */
    DataLengthInOut = DATA_LENGTH_MAX;        /*  the VDHDevIOCtl call       */

    switch ( reqhdr->command )
    {
        case DOS_IOCTL_INPUT:
            control_block = PFROMVADDR ( FP_SEG ((RH(3) reqhdr)->control_block),
                                         FP_OFF ((RH(3) reqhdr)->control_block)
                                       );
            if ( *control_block == RADDR )    /* dos driver address          */
            {
                rc = NO_ERROR;
                * ((PULONG) (control_block + 1)) = (ULONG) DosDDHeader;
                break;
            }                              /* return address of device header*/

            ioctl = IoctlInputCodes [*control_block];

            if ( ioctl.code == MEDIA_CHANGED_EXCEPTION )  /* not in OS/2     */
            {
                media_check_in_progress = TRUE;
                ioctl.code = RETURN_VOLUME_SIZE;    /* generates the error   */
                save_volume_size = * ( (PULONG) ( control_block + 1 ) );
            }
            else
            {
                media_check_in_progress = FALSE;
            }

            parm_list = control_block - 3;         /* "CD01" then param then */
                                                   /*   data area in DOS     */
            if ( *control_block == LOCHEAD  ||
                 *control_block == SECTSIZE ||
                 *control_block == TNOINFO )
            {
                data_area = control_block + 2;     /* skip over a param      */
            }
            else
            {
                data_area = control_block + 1;     /* skip over control code */
            }

            saveCD01area = * ( (PULONG) ( control_block - 3 ) );
            * ( (PULONG) ( control_block - 3 ) ) = CD01;

            rc = VDHDevIOCtl ( handle,
                               ioctl.category,
                               ioctl.code,
                               parm_list,
                               PARM_LENGTH_MAX,
                               &ParmLengthInOut,
                               data_area,
                               DATA_LENGTH_MAX,
                               &DataLengthInOut );

            if ( rc == VDH_FAILURE )
                rc = VDHGetError ();
            else
                rc = NO_ERROR;

            * ( (PULONG) ( control_block - 3 ) ) = saveCD01area;
            if ( media_check_in_progress )
            {
                * ( (PULONG) ( control_block + 1 ) ) = save_volume_size;
                if ( (rc & 0xFF) == ERROR_I24_UNCERTAIN_MEDIA )
                    * ( (PUCHAR) ( control_block + 1 ) ) = MEDIA_UNCERTAIN;
                else
                    * ( (PUCHAR) ( control_block + 1 ) ) = MEDIA_NOT_CHANGED;
            }
            break;

        case DOS_IOCTL_OUTPUT:
            control_block = PFROMVADDR ( FP_SEG ((RH(3) reqhdr)->control_block),
                                         FP_OFF ((RH(3) reqhdr)->control_block)
                                       );

            ioctl = IoctlOutputCodes [*control_block];
            parm_list = control_block - 3;          /* CD01 then param and   */
            data_area = control_block + 1;          /*  data which overlap   */

            saveCD01area = * ( (PULONG) ( control_block - 3 ) );
            * ( (PULONG) ( control_block - 3 ) ) = CD01;

            rc = VDHDevIOCtl ( handle,
                               ioctl.category,
                               ioctl.code,
                               parm_list,
                               PARM_LENGTH_MAX,
                               &ParmLengthInOut,
                               data_area,
                               DATA_LENGTH_MAX,
                               &DataLengthInOut );

            if ( rc == VDH_FAILURE )
                rc = VDHGetError ();
            else
                rc = NO_ERROR;

            * ( (PULONG) ( control_block - 3 ) ) = saveCD01area;
            break;

        case DOS_READ_LONG:                         /* OS/2 read command (4) */
            if ( (RH(128) reqhdr)->address_mode != HSG_ADDRESS  ||
                                 (RH(128) reqhdr)->read_mode != COOKED_MODE )
            {
                rc = DEVIOCTL_ERROR + ERROR_I24_BAD_COMMAND;
                break;
            }
            rc = VDHSeek ( handle,
                           (RH(128) reqhdr)->start_sector * CDROM_SECTOR_SIZE,
                           VDHSK_ABSOLUTE );
            if ( rc == VDH_ERROR )
            {
                rc = VDHGetError ();
                break;
            }
            rc = VDHRead ( handle,
                           PFROMVADDR (FP_SEG ((RH(128) reqhdr)->transfer_area),
                                       FP_OFF ((RH(128) reqhdr)->transfer_area)
                                      ),
                           (ULONG) (RH(128) reqhdr)->transfer_count *
                                                     CDROM_SECTOR_SIZE
                         );
            if ( rc == VDH_ERROR )
            {
                rc = VDHGetError ();
                (RH(128) reqhdr)->transfer_count = 0;
            }
            else
            {
                rc = NO_ERROR;
            }
            break;

        case DOS_READ_LONG_PREFETCH:                /* fall thru             */
        case DOS_SEEK:
            control_block = (PCHAR) reqhdr;
            parm_list = control_block + 9;          /* CD01 then params      */
            data_area = 0;

            save_transfer_area = (RH(131) reqhdr)->transfer_area;
            * ( (PULONG) &(RH(131) reqhdr)->transfer_area ) =
                                            (RH(131) reqhdr)->start_sector;

            saveCD01area = * ( (PULONG) ( control_block + 9 ) );
            * ( (PULONG) ( control_block + 9 ) ) = CD01;

            rc = VDHDevIOCtl ( handle,
                               0x0080,
                               0x0050,
                               parm_list,
                               PARM_LENGTH_MAX,
                               &ParmLengthInOut,
                               data_area,
                               DATA_LENGTH_MAX,
                               &DataLengthInOut );

            if ( rc == VDH_FAILURE )
                rc = VDHGetError ();
            else
                rc = NO_ERROR;

            * ( (PULONG) ( control_block + 9 ) ) = saveCD01area;
            (RH(131) reqhdr)->transfer_area = save_transfer_area;
            break;

        case DOS_PLAY_AUDIO:
            control_block = (PCHAR) reqhdr;
            parm_list = control_block + 9;          /* CD01 then params      */
            data_area = 0;

            save_number_of_sectors = (RH(132) reqhdr)->number_of_sectors;
            * ( (PULONG) &(RH(132) reqhdr)->number_of_sectors ) =
                     AddSectors ( (RH(132) reqhdr)->start_sector,
                                        (RH(132) reqhdr)->number_of_sectors,
                                        (RH(132) reqhdr)->address_mode
                                      );
            saveCD01area = * ( (PULONG) ( control_block + 9 ) );
            * ( (PULONG) ( control_block + 9 ) ) = CD01;

            rc = VDHDevIOCtl ( handle,
                               0x0081,
                               0x0050,
                               parm_list,
                               PARM_LENGTH_MAX,
                               &ParmLengthInOut,
                               data_area,
                               DATA_LENGTH_MAX,
                               &DataLengthInOut );

            if ( rc == VDH_FAILURE )
                rc = VDHGetError ();
            else
                rc = NO_ERROR;

            * ( (PULONG) ( control_block + 9 ) ) = saveCD01area;
            * ( (PULONG) &(RH(132) reqhdr)->number_of_sectors ) =
                                                    save_number_of_sectors;
            break;

        case DOS_STOP_AUDIO:
            control_block = (PCHAR) reqhdr;
            parm_list = control_block + 9;          /* CD01 then params      */
            data_area = 0;

            saveCD01area = * ( (PULONG) ( control_block + 9 ) );
            * ( (PULONG) ( control_block + 9 ) ) = CD01;

            rc = VDHDevIOCtl ( handle,
                               0x0081,
                               0x0051,
                               parm_list,
                               PARM_LENGTH_MAX,
                               &ParmLengthInOut,
                               data_area,
                               DATA_LENGTH_MAX,
                               &DataLengthInOut );

            if ( rc == VDH_FAILURE )
                rc = VDHGetError ();
            else
                rc = NO_ERROR;

            * ( (PULONG) ( control_block + 9 ) ) = saveCD01area;
            break;

        case DOS_RESUME_AUDIO:
            control_block = (PCHAR) reqhdr;
            parm_list = control_block + 9;          /* CD01 then params      */
            data_area = 0;

            saveCD01area = * ( (PULONG) ( control_block + 9 ) );
            * ( (PULONG) ( control_block + 9 ) ) = CD01;

            rc = VDHDevIOCtl ( handle,
                               0x0081,
                               0x0052,
                               parm_list,
                               PARM_LENGTH_MAX,
                               &ParmLengthInOut,
                               data_area,
                               DATA_LENGTH_MAX,
                               &DataLengthInOut );

            if ( rc == VDH_FAILURE )
                rc = VDHGetError ();
            else
                rc = NO_ERROR;

            * ( (PULONG) ( control_block + 9 ) ) = saveCD01area;
            break;
        default:
            switch ( reqhdr->command )
            {
                case DOS_DEVICE_OPEN:
                    ResetMedia ( drive_number );
                case DOS_INPUT_FLUSH:
                case DOS_DEVICE_CLOSE:
                    rc = NO_ERROR;
                    break;
                default:
                    rc = DEVIOCTL_ERROR + ERROR_I24_BAD_COMMAND;
            }
    }

    reqhdr->status = MapOS2toDosCode ( rc );
    if ( (reqhdr->status & 0xFF) == ERROR_I24_WRONG_DISK )   //INV_DISK_CHANGE
        ResetMedia ( drive_number );
    else
        reqhdr->status |= GetPlayStatus ( handle );

    return;
}


/****************************************************************************
 *
 * FUNCTION NAME = ResetMedia
 *
 * DESCRIPTION   = This function resets the uncertain media condition for the
 *                 device.  It is necessary because we are bypassing the file
 *                 system with user-defined generic ioctls.
 *
 *                 CONTEXT:
 *                    VDM Task-time
 *
 *                 PSEUDOCODE:
 *                    Close and open the drive handle to reset media.
 *
 * INPUT         = drive_number - of the CD-ROM (0=A, 1=B...)
 *
 * OUTPUT        = void
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

VOID PRIVENTRY ResetMedia (ULONG drive_number)
{
    register
    ULONG   rc;

    VDHClose ( DriveHandle [ drive_number ] );

    rc = VDHOpen ( GET_DRIVE_STRING ( drive_number ),
                   &FileHandle,
                   &ActionTaken,
                   FILESIZE,
                   FILEATTRIBUTE,
                   OPENFLAG,
                   OPENMODE,
                   EABUF );

    if ( rc == VDH_FAILURE )
    {
        rc = VDHGetError ();                            /* for debugging     */
        FileHandle = NULL;
    }

    DriveHandle [ drive_number ] = FileHandle;          /* NULL if error     */

}


/****************************************************************************
 *
 * FUNCTION NAME = GetDriveHandle
 *
 * DESCRIPTION   = This function maps the drive number to a possibly already
 *                 opened drive handle.  If the handle is not opened, one is
 *                 opened and returned.  If the handle is opened, a media check
 *                 is performed to determine if the handle is still valid.  If
 *                 the handle is invalid, the handle is closed and a new handle
 *                 is opened, stored, and returned. Performing a media check is
 *                 faster than closing and openning the file handle especially
 *                 for each MSCDEX api.  The open is required to (re)mount the
 *                 removable media volume.  We only want to take the
 *                 performance penalty of open/close when the media has truly
 *                 changed.
 *
 *                 CONTEXT:
 *                    VDM Task-time
 *
 *                 PSEUDOCODE:
 *                    If the drive is not open,
 *                      open drive and save handle
 *                    else the drive is open
 *                      perform media check
 *                      if media changed
 *                         close handle, open new handle and save it
 *                      endif
 *                    endif
 *                    return handle
 *
 * INPUT         = drive_number - of the CD-ROM (0=A, 1=B...)
 *
 * OUTPUT        = DASD-open file handle  (NULL == failure)
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

HFILE PRIVENTRY GetDriveHandle (ULONG drive_number)
{
    register
    ULONG   rc;

    FileHandle = DriveHandle [ drive_number ];

    if ( FileHandle == NULL )
    {
        rc = VDHOpen ( GET_DRIVE_STRING ( drive_number ),
                       &FileHandle,
                       &ActionTaken,
                       FILESIZE,
                       FILEATTRIBUTE,
                       OPENFLAG,
                       OPENMODE,
                       EABUF );

        if ( rc == VDH_FAILURE )
        {
            rc = VDHGetError ();                          /* for debugging   */
            FileHandle = NULL;
        }
        DriveHandle [ drive_number ] = FileHandle;        /* NULL if error   */
    }
    else
    {
        DataArea[0] = 0x0000;
        DataLengthInOut = sizeof(USHORT);
        ParmLengthInOut = 0x0000;

        /* call CD-ROM IFS to see if media has changed */
        rc = VDHFSCtl((PVOID)&DataArea,
                      (ULONG)DataLengthInOut,
                      (PVOID)&DataLengthInOut,
                      (PVOID)&ParmList,
                      (ULONG)ParmLengthInOut,
                      (PVOID)&ParmLengthInOut,
                      (ULONG)FSCTL_ARG_MEDIACHG,
                      (PSZ)NULL,
                      FileHandle,
                      FSCTL_HANDLE);

        if ( rc == VDH_FAILURE ) {
            rc = VDHGetError ();
        }

        if (DataArea[0] == ERROR_VOLUME_CHANGED)
        {
            ResetMedia ( drive_number );
            FileHandle = DriveHandle [ drive_number ];
        }
    }
    return FileHandle;
}


/****************************************************************************
 *
 * FUNCTION NAME = MapOS2toDosCode
 *
 * DESCRIPTION   =
 *      This function converts the OS/2 return code to a DOS device
 *      driver status code.
 *
 *      CONTEXT:
 *         VDM Task-time
 *
 *      TYPES OF OS/2 CODES:
 *         0000  = no error
 *         FFxx  = device driver error code
 *         other = system error code
 *
 *      PSEUDOCODE:
 *         Map the code according to its type.
 *
 * INPUT         = OS/2 rc
 *
 * OUTPUT        = DOS status code
 *
 * RETURN-NORMAL =
 *
 * RETURN-ERROR  =
 *
 ****************************************************************************/

USHORT PRIVENTRY MapOS2toDosCode (register ULONG rc)
{
    register
    ULONG   dos_rc;

    if (rc == 0)
    {
       dos_rc = DONE;
    }
    else
       if (((USHORT)rc & 0xFF00) == DEVIOCTL_ERROR)
       {
          if (((USHORT)rc & 0xFF) == ERROR_I24_UNCERTAIN_MEDIA)
          {
             dos_rc = ERROR + DONE + ERROR_I24_WRONG_DISK;  //INV_DISK_CHANGE
          }
          else
          {
             dos_rc = ERROR + DONE + ((USHORT) rc & 0xFF);
          }
       }
       else
       {                                     /* DosCall system error code    */
          if (rc >= ERROR_WRITE_PROTECT && rc <= ERROR_WRONG_DISK)
          {
             dos_rc = ERROR + DONE + (USHORT)rc - ERROR_WRITE_PROTECT;
          }
          else
          {
             dos_rc = ERROR + DONE + ERROR_I24_GEN_FAILURE;
          }
       }
    if (dos_rc > ERROR + DONE + ERROR_I24_WRONG_DISK)     //INV_DISK_CHANGE
    {
       dos_rc = ERROR + DONE + ERROR_I24_GEN_FAILURE;
    }
    return (USHORT)dos_rc;

}


/****************************************************************************
 *
 * FUNCTION NAME = GetPlayStatus
 *
 * DESCRIPTION   = This function determines if the drive is currently playing
 *                 audio.
 *
 *                 CONTEXT:
 *                    VDM Task-time
 *
 *                 PSEUDOCODE:
 *                    Make a device-status call to the OS/2 CD-ROM driver.
 *                    If playing, set the busy bit.
 *
 * INPUT         = DASD-open file handle
 *
 * OUTPUT        = 0200h == playing
 *                 0000h == not playing
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

USHORT PRIVENTRY GetPlayStatus (HFILE handle)
{
    register
    ULONG   rc;

    ParmLengthInOut = PARM_LENGTH_MAX;      /* these are just dummies for   */
    DataLengthInOut = DATA_LENGTH_MAX;      /*  the VDHDevIOCtl call        */

    *ParmList = CD01;

    rc = VDHDevIOCtl ( handle,
                       0x0080,
                       0x0060,              /* device status                */
                       ParmList,
                       PARM_LENGTH_MAX,
                       &ParmLengthInOut,
                       DataArea,
                       DATA_LENGTH_MAX,
                       &DataLengthInOut );

    if ( rc == VDH_FAILURE )
        rc = VDHGetError ();
    else
        rc = NO_ERROR;

    if ( rc == NO_ERROR  &&  (*DataArea & DRIVE_PLAYING) )
        return BUSY;                        /* busy bit == playing in DOS   */
    else
        return 0;
}


/****************************************************************************
 *
 * FUNCTION NAME = HsgToRedbook
 *
 * DESCRIPTION   = This function converts an HSG number-of-sectors to a Redbook
 *                 (min:sec:frames) number-of-sectors.
 *
 *                 CONTEXT:
 *                    VDM Task-time
 *
 *                 PSEUDOCODE:
 *                    Convert long relative sector number to
 *                    long 0:min:sec:frames.
 *                      75 frames per second
 *                      60 seconds per minute
 *                      1 frame == 1 HSG sector
 *
 * INPUT         = HSG number_of_sectors
 *
 * OUTPUT        = Redbook number_of_sectors
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

ULONG PRIVENTRY HsgToRedbook (register ULONG hsg_value)
{
    UCHAR   minutes;
    UCHAR   seconds;
    UCHAR   frames;
    register
    ULONG   redbook;

    redbook = 0;

    redbook |= ( (hsg_value / (60 * 75)) << 16 );       /* minutes           */
    hsg_value %= (60 * 75);                             /* remaining frames  */
    redbook |= ( (hsg_value / 75) << 8 );               /* seconds           */
    redbook |= ( hsg_value % 75 );                      /* frames            */

    return ( redbook );
}


/****************************************************************************
 *
 * FUNCTION NAME = AddSectors
 *
 * DESCRIPTION   = This function adds two sector values, returning the sum. It
 *                 is necessary because the OS/2 call requires the ending sector
 *                 number, but the DOS parameter is number-of-sectors.
 *
 *                 CONTEXT:
 *                    VDM Task-time
 *
 *                 PSEUDOCODE:
 *                    If HSG (normal numbers), just add them.
 *                    If RedBook (minutes, seconds, frames), convert the HSG
 *                      value to redbook and then add each field separately,
 *                      with carry.
 *
 * INPUT         = start_sector
 *                 number_of_sectors
 *                 address_mode - HSG or RedBook
 *
 * OUTPUT        = sum of the sectors
 *                 0 = invalid address mode
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

ULONG PRIVENTRY AddSectors (register ULONG start_sector,
                            register ULONG number_of_sectors,
                                     ULONG address_mode)
{
    register
    ULONG   value;
    UCHAR   carry;
    UCHAR   byte;

    switch ( address_mode )
    {
        case HSG_ADDRESS:
            value = start_sector + number_of_sectors;
            break;

        case REDBOOK_ADDRESS:
            number_of_sectors = HsgToRedbook ( number_of_sectors );
            value = 0;
            byte = FRAMES ( start_sector ) + FRAMES ( number_of_sectors );
            if ( byte > 74 )
            {
                byte -= 75;
                carry = 1;
            }
            else
            {
                carry = 0;
            }
            value |= byte;
            byte = SECONDS ( start_sector ) + SECONDS ( number_of_sectors ) +
                   carry;
            if ( byte > 59 )
            {
                byte -= 60;
                carry = 1;
            }
            else
            {
                carry = 0;
            }
            value |= byte << 8;
            byte = MINUTES ( start_sector ) + MINUTES ( number_of_sectors ) +
                   carry;
            value |= byte << 16;
            break;

        default:
            value = 0;
    }

    return value;
}


#pragma END_SWAP_CODE
