/*
**  cd.device Test
**  Written by John J. Szucs
**  Copyright © 1993-1999 Amiga, Inc.
**  All Rights Reserved
*/

/*
**  ANSI includes
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

/*
**  System includes
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/interrupts.h>
#include <exec/io.h>

#include <utility/tagitem.h>

#include <dos/dos.h>

#include <devices/cd.h>

#include <devices/trackdisk.h>

#include <rexx/rxslib.h>
#include <rexx/storage.h>
#include <rexx/errors.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/rexxsyslib_protos.h>
#include <clib/alib_protos.h>

/*
**  Local includes
*/

#include "simplerexx.h"
#include "cdtest.h"

/****** cd/cdAddChangeInt ******************************************
*
*   NAME
*       cdAddChangeInt -- add change interrupt
*
*   SYNOPSIS
*       result=cdAddChangeInt(rxMsg,args,error);
*
*       STRPTR cdAddChangeInt(struct RexxMsg *rxMsg,STRPTR args,LONG *error);
*
*   FUNCTION
*       Add change interrupt.
*
*   INPUTS
*       rxMsg   -   ARexx message
*       args    -   arguments from ARexx
*       error   -   pointer to error value
*
*   RESULT
*       *error  -   Error code (if any)
*       result  -   result string
*
*   EXAMPLE
*
*   NOTES
*
*   BUGS
*       Normal procedure for handling exec.library I/O requests
*       requires that an I/O request be completed before it is
*       re-used. However, for historical reasons, the *_ADDCHANGEINT
*       and *_ADDFRAMEINT commands of trackdisk.device, scsi.device,
*       and cd.device require the caller to send the I/O request
*       to add the interrupt and re-use the I/O request (without
*       calling exec.library/WaitIO()) with a *_REMCHANGEINT
*       or *_REMFRAMEINT command to remove the interrupt.
*
*       This causes IOTorture hits and is, in my humble opinion,
*       a Bad Thing(TM). However, it is apparently well beyond the
*       point where this behavior could be changed.
*
*   SEE ALSO
*       cdRemChangeInt
*
******************************************************************************
*
*/

STRPTR cdAddChangeInt(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    /* Set-up disk change interrupt */
    changeInterrupt.is_Node.ln_Name="CD Disk Change";
    changeInterrupt.is_Data=(APTR) COOKIE_CHANGEINT;
    changeInterrupt.is_Code=(void (*)()) changeInterruptCode;
    changeIntCall=changeIntBadData=0L;

    /* Add disk change interrupt */
    cdRequest->io_Command=CD_ADDCHANGEINT;
    cdRequest->io_Length=sizeof(changeInterrupt);
    cdRequest->io_Data=&changeInterrupt;
    SendIO(cdRequest);

    /* Return w/o result */
    return(NULL);

}

/****** cd/cdAddFrameInt ******************************************
*
*   NAME
*       cdAddChangeInt -- add frame interrupt
*
*   SYNOPSIS
*       result=cdAddFrameInt(rxMsg,args,error);
*
*       STRPTR cdAddFrameInt(struct RexxMsg *rxMsg,STRPTR args,LONG *error);
*
*   FUNCTION
*       Add frame interrupt.
*
*   INPUTS
*       rxMsg   -   ARexx message
*       args    -   arguments from ARexx
*       error   -   pointer to error value
*
*   RESULT
*       *error  -   Error code (if any)
*       result  -   result string
*
*   EXAMPLE
*
*   NOTES
*
*   BUGS
*       Normal procedure for handling exec.library I/O requests
*       requires that an I/O request be completed before it is
*       re-used. However, for historical reasons, the *_ADDCHANGEINT
*       and *_ADDFRAMEINT commands of trackdisk.device, scsi.device,
*       and cd.device require the caller to send the I/O request
*       to add the interrupt and re-use the I/O request (without
*       calling exec.library/WaitIO()) with a *_REMCHANGEINT
*       or *_REMFRAMEINT command to remove the interrupt.
*
*       This causes IOTorture hits and is, in my humble opinion,
*       a Bad Thing(TM). However, it is apparently well beyond the
*       point where this behavior could be changed.
*
*   SEE ALSO
*       cdRemFrameInt
*
******************************************************************************
*
*/

STRPTR cdAddFrameInt(struct RexxMsg *rxMsg,STRPTR  args,LONG *error)
{

    if (debugMode) {
        printf("Initializing frameInterrupt\n");
    }

    /* Set-up frame interrupt */
    frameInterrupt.is_Node.ln_Pri=0;
    frameInterrupt.is_Node.ln_Name="CD Frame";
    frameInterrupt.is_Data=(APTR) COOKIE_FRAMEINT;
    frameInterrupt.is_Code=(void (*)()) frameInterruptCode;
    frameIntCall=frameIntBadData=0L;

    if (debugMode) {
        printf("frameInterrupt initialized: is_Data=$%08lx, is_Code=$%08lx\n",
            frameInterrupt.is_Data,frameInterrupt.is_Code);
    }

    /* Add frame interrupt */
    cdRequest->io_Command=CD_ADDFRAMEINT;
    cdRequest->io_Length=sizeof(frameInterrupt);
    cdRequest->io_Data=&frameInterrupt;
    SendIO(cdRequest);
    if (debugMode) {
        printf("CD_ADDFRAMEINT good\n");
    }

    /* Return w/o result */
    return(NULL);

}

STRPTR cdAttenuate(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG *duration;
        LONG *target;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"DURATION/N/A,TARGET/N/A",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    cdRequest->io_Command=CD_ATTENUATE;
    cdRequest->io_Length=*options.duration;
    cdRequest->io_Offset=*options.target;
    if (noCD) {
        ioError=-1;
    } else {
        ioError=DoIO(cdRequest);
    }
    if (ioError) {
        if (debugMode) {
            printf("CD_ATTENUATE I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        /* Return current volume */
        bsprintf(rexxResult,"%ld",cdRequest->io_Actual);
        result=rexxResult;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);

}

STRPTR cdChangeNum(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    BYTE ioError;

    STRPTR result=NULL;

    cdRequest->io_Command=CD_CHANGENUM;
    if (noCD) {
        ioError=-1;
    } else {
        ioError=DoIO(cdRequest);
    }
    if (ioError) {
        if (debugMode) {
            printf("CD_CHANGENUM I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        /* Return disk-change counter */
        bsprintf(rexxResult,"%ld",cdRequest->io_Actual);
        result=rexxResult;
    }

    /* Return result */
    return(result);

}

STRPTR cdChangeState(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

                    BYTE ioError;

    STRPTR result=NULL;

    cdRequest->io_Command=CD_CHANGESTATE;
    if (noCD) {
        ioError=-1;
    } else {
        ioError=DoIO(cdRequest);
    }
    if (ioError) {
        if (debugMode) {
            printf("CD_CHANGESTATE I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        /* Return disk-change state */
        bsprintf(rexxResult,"%ld",cdRequest->io_Actual);
        result=rexxResult;
    }

    /* Return result */
    return(result);

}

/*
 *  CD_CONFIG TAGCD_* definitions conflict with standard system tag definitions
 *  in utility/utility.h.
 *
 *  As a result, this function is implemented in a less than optimal manner.
 *
 */


STRPTR cdConfig(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG *playSpeed;
        LONG *readSpeed;
        LONG *readXLSpeed;
        LONG *sectorSize;
        LONG xlECC;
        LONG noXLECC;
        LONG ejectReset;
        LONG noEjectReset;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    static struct TagItem configTags[CDCONFIG_MAXTAGS];
    int thisTag=0;

    /* Parse arguments */
    rdArgs=obtainArgs(args,
       "PLAYSPEED/N,READSPEED/N,READXLSPEED/N,SECTORSIZE/N,XLECC/S,NOXLECC/S,EJECTRESET/S,NOEJECTRESET/S",
       &options,sizeof(options));
    if (!rdArgs) {
       *error=RC_ERROR;
       return(NULL);
    }

    /* Play speed */
    if (options.playSpeed) {
        configTags[thisTag].ti_Tag=TAGCD_PLAYSPEED;
        configTags[thisTag].ti_Data=*options.playSpeed;
        thisTag++;
    }
    if (debugMode) {
        printf(options.playSpeed?"PlaySpeed=%d\n":"PlaySpeed=Not specified\n",
            options.playSpeed?*options.playSpeed:NULL);
    }

    /* Read speed */
    if (options.readSpeed) {
        configTags[thisTag].ti_Tag=TAGCD_READSPEED;
        configTags[thisTag].ti_Data=*options.readSpeed;
        thisTag++;
    }
    if (debugMode) {
        printf(options.readSpeed?"ReadSpeed=%d\n":"ReadSpeed=Not specified\n",
            options.readSpeed?*options.readSpeed:NULL);
    }

    /* XL Read speed */
    if (options.readXLSpeed) {
        configTags[thisTag].ti_Tag=TAGCD_READSPEED;
        configTags[thisTag].ti_Data=*options.readXLSpeed;
        thisTag++;
    }
    if (debugMode) {
        printf(options.readXLSpeed?"ReadXLSpeed=%d\n":"ReadXLSpeed=Not specified\n",
            options.readXLSpeed?*options.readXLSpeed:NULL);
    }

    /* Sector size */
    if (options.sectorSize) {
        configTags[thisTag].ti_Tag=TAGCD_SECTORSIZE;
        configTags[thisTag].ti_Data=*options.sectorSize;
        thisTag++;
    }
    if (debugMode) {
        printf(options.sectorSize?"SectorSize=%d\n":"SectorSize=Not specified\n",
            options.sectorSize?*options.sectorSize:NULL);
    }

    /* XL ECC */
    if (options.xlECC|options.noXLECC) {
        configTags[thisTag].ti_Tag=TAGCD_XLECC;
        configTags[thisTag].ti_Data=options.xlECC?TRUE:FALSE;
        thisTag++;
    }
    if (debugMode) {
        printf(options.xlECC?"XLECC=%d\n":"XLECC=Not specified\n",
            options.xlECC?"TRUE":"FALSE");
    }

    /* Reset on eject */
    if (options.ejectReset|options.noEjectReset) {
        configTags[thisTag].ti_Tag=TAGCD_EJECTRESET;
        configTags[thisTag].ti_Data=
            (options.ejectReset&options.noEjectReset)?0xFFFFFFFF:(options.ejectReset?TRUE:FALSE);
        thisTag++;
    }
    if (debugMode) {
        printf(options.ejectReset?"EjectReset=%d\n":"EjectReset=Not specified\n",
            options.ejectReset?"TRUE":"FALSE");
    }

    /* Terminate tag array */
    configTags[thisTag].ti_Tag=TAG_DONE;

    cdRequest->io_Command=CD_CONFIG;
    cdRequest->io_Data=configTags;
    cdRequest->io_Length=0;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_CONFIG I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);

}

STRPTR cdEject(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG open;
        LONG close;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"OPEN/S,CLOSE/S",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    cdRequest->io_Command=CD_EJECT;
    cdRequest->io_Length=options.open?1:(options.close?0:-1);
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_EJECT I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        /* Return current door state */
        bsprintf(rexxResult,"%ld",cdRequest->io_Actual);
        result=rexxResult;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);

}

STRPTR cdGetGeometry(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        STRPTR stem;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    struct DriveGeometry driveGeometry;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"STEM/A",&options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Fetch drive geometry */
    cdRequest->io_Command=CD_GETGEOMETRY;
    cdRequest->io_Length=sizeof(driveGeometry);
    cdRequest->io_Data=&driveGeometry;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_GETGEOMETRY I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    } else {

        /* Return drive geometry in ARexx stem variable */
        setStemVarInt(rxMsg,options.stem,"SectorSize",
            driveGeometry.dg_SectorSize);
        setStemVarInt(rxMsg,options.stem,"TotalSectors",
            driveGeometry.dg_TotalSectors);
        setStemVarInt(rxMsg,options.stem,"Cylinders",
            driveGeometry.dg_Cylinders);
        setStemVarInt(rxMsg,options.stem,"CylSectors",
            driveGeometry.dg_CylSectors);
        setStemVarInt(rxMsg,options.stem,"Heads",
            driveGeometry.dg_Heads);
        setStemVarInt(rxMsg,options.stem,"TrackSectors",
            driveGeometry.dg_TrackSectors);
        setStemVarInt(rxMsg,options.stem,"BufMemType",
            driveGeometry.dg_BufMemType);
        setStemVarInt(rxMsg,options.stem,"DeviceType",
            driveGeometry.dg_DeviceType);
        setStemVarInt(rxMsg,options.stem,"Removable",
            driveGeometry.dg_Flags&DGF_REMOVABLE);

    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return */
    return(NULL);

}

STRPTR cdInfo(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        STRPTR stem;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    struct CDInfo cdInfo;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"STEM/A",&options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Fetch device information */
    cdRequest->io_Command=CD_INFO;
    cdRequest->io_Length=sizeof(cdInfo);
    cdRequest->io_Data=&cdInfo;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_INFO I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    } else {

        /* Return device information in ARexx stem variable */
        setStemVarInt(rxMsg,options.stem,"PlaySpeed",
            cdInfo.PlaySpeed);
        if (debugMode) {
            printf("PlaySpeed=%d\n",cdInfo.PlaySpeed);
        }
        setStemVarInt(rxMsg,options.stem,"ReadSpeed",
            cdInfo.ReadSpeed);
        if (debugMode) {
            printf("ReadSpeed=%d\n",cdInfo.ReadSpeed);
        }
        setStemVarInt(rxMsg,options.stem,"ReadXLSpeed",
            cdInfo.ReadXLSpeed);
        if (debugMode) {
            printf("ReadXLSpeed=%d\n",cdInfo.ReadXLSpeed);
        }
        setStemVarInt(rxMsg,options.stem,"SectorSize",
            cdInfo.SectorSize);
        if (debugMode) {
            printf("SectorSize=%d\n",cdInfo.SectorSize);
        }
        setStemVarInt(rxMsg,options.stem,"XLECC",
            cdInfo.XLECC);
        if (debugMode) {
            printf("XLECC=%d\n",cdInfo.XLECC);
        }
        setStemVarInt(rxMsg,options.stem,"EjectReset",
            cdInfo.EjectReset);
        if (debugMode) {
            printf("EjectReset=%d\n",cdInfo.EjectReset);
        }
        setStemVarInt(rxMsg,options.stem,"MaxSpeed",
            cdInfo.MaxSpeed);
        if (debugMode) {
            printf("MaxSpeed=%d\n",cdInfo.MaxSpeed);
        }
        setStemVarInt(rxMsg,options.stem,"AudioPrecision",
            cdInfo.AudioPrecision);
        if (debugMode) {
            printf("AudioPrecision=%d\n",cdInfo.AudioPrecision);
        }
        if (debugMode) {
            printf("Status=$%04x\n",cdInfo.Status);
        }
        setStemVarInt(rxMsg,options.stem,"Closed",
            cdInfo.Status&CDSTSF_CLOSED);
        if (debugMode) {
            printf("Closed=%d\n",cdInfo.Status&CDSTSF_CLOSED);
        }
        setStemVarInt(rxMsg,options.stem,"Disk",
            cdInfo.Status&CDSTSF_DISK);
        if (debugMode) {
            printf("Disk=%d\n",cdInfo.Status&CDSTSF_DISK);
        }
        setStemVarInt(rxMsg,options.stem,"Spin",
            cdInfo.Status&CDSTSF_SPIN);
        if (debugMode) {
            printf("Spin=%d\n",cdInfo.Status&CDSTSF_SPIN);
        }
        setStemVarInt(rxMsg,options.stem,"TOC",
            cdInfo.Status&CDSTSF_TOC);
        if (debugMode) {
            printf("TOC=%d\n",cdInfo.Status&CDSTSF_TOC);
        }
        setStemVarInt(rxMsg,options.stem,"CDROM",
            cdInfo.Status&CDSTSF_CDROM);
        if (debugMode) {
            printf("CDROM=%d\n",cdInfo.Status&CDSTSF_CDROM);
        }
        setStemVarInt(rxMsg,options.stem,"Playing",
            cdInfo.Status&CDSTSF_PLAYING);
        if (debugMode) {
            printf("Playing=%d\n",cdInfo.Status&CDSTSF_PLAYING);
        }
        setStemVarInt(rxMsg,options.stem,"Paused",
            cdInfo.Status&CDSTSF_PAUSED);
        if (debugMode) {
            printf("Paused=%d\n",cdInfo.Status&CDSTSF_PAUSED);
        }
        setStemVarInt(rxMsg,options.stem,"Search",
            cdInfo.Status&CDSTSF_SEARCH);
        if (debugMode) {
            printf("Search=%d\n",cdInfo.Status&CDSTSF_SEARCH);
        }
        setStemVarInt(rxMsg,options.stem,"Direction",
            cdInfo.Status&CDSTSF_DIRECTION);
        if (debugMode) {
            printf("Direction=%d\n",cdInfo.Status&CDSTSF_DIRECTION);
        }

    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return */
    return(result);

}

STRPTR cdMotor(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG on;
        LONG off;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"ON/S,OFF/S",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    cdRequest->io_Command=CD_MOTOR;
    cdRequest->io_Length=options.on?1:(options.off?0:-1);
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_EJECT I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        /* Return current motor state */
        bsprintf(rexxResult,"%ld",cdRequest->io_Actual);
        result=rexxResult;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);

}

STRPTR cdPause(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG on;
        LONG off;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"ON/S,OFF/S",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    cdRequest->io_Command=CD_PAUSE;
    cdRequest->io_Length=options.on?1:(options.off?0:-1);
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_PAUSE I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {

        /* Return pause state */
        bsprintf(rexxResult,"%ld",cdRequest->io_Actual);
        result=rexxResult;

    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);

}

STRPTR cdPlayLSN(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG *start;
        LONG *length;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"START/N/A,LENGTH/N/A",&options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Play audio */
    cdRequest->io_Command=CD_PLAYLSN;
    cdRequest->io_Length=*options.length;
    cdRequest->io_Offset=*options.start;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_PLAYLSN I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        result=NULL;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);

}

STRPTR cdPlayMSF(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        STRPTR start;
        STRPTR length;
    } options;
    struct RDArgs *rdArgs;
    ULONG startMSF, lengthMSF;

    BYTE ioError;

    STRPTR result=NULL;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"START/A,LENGTH/A",&options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }
    if (debugMode) {
        printf("cdPlayMSF -- start=%s, length=%s\n",options.start,
            options.length);
    }
    startMSF=strToMSF(options.start);
    lengthMSF=strToMSF(options.length);
    if (debugMode) {
        printf("cdPlayMSF -- start=$%08lx, length=$%08lx\n",startMSF,lengthMSF);
    }

    /* Play audio */
    cdRequest->io_Command=CD_PLAYMSF;
    cdRequest->io_Length=lengthMSF;
    cdRequest->io_Offset=startMSF;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_PLAYMSF I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        result=NULL;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);

}

STRPTR cdPlayTrack(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG *start;
        LONG *length;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"START/N/A,LENGTH/N/A",&options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Play track */
    cdRequest->io_Command=CD_PLAYTRACK;
    cdRequest->io_Length=*options.length;
    cdRequest->io_Offset=*options.start;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_PLAYTRACK I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        result=NULL;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return result */
    return(result);


}

STRPTR cdProtStatus(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    BYTE ioError;

    STRPTR result=NULL;

    cdRequest->io_Command=CD_PROTSTATUS;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_PROTSTATUS I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {
        /* Return protection status */
        bsprintf(rexxResult,"%ld",cdRequest->io_Actual);
        result=rexxResult;
    }

    /* Return result */
    return(result);

}

STRPTR cdQCodeLSN(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        STRPTR stem;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    struct QCode qCode;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"STEM/A",&options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Fetch Q-Code */
    cdRequest->io_Command=CD_QCODELSN;
    cdRequest->io_Length=0;
    cdRequest->io_Data=&qCode;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_QCODELSN I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    } else {

        /* Return QCode data in ARexx stem variable */
        setStemVarInt(rxMsg,options.stem,"CtlAdr",qCode.CtlAdr);
        setStemVarInt(rxMsg,options.stem,"Track",qCode.Track);
        setStemVarInt(rxMsg,options.stem,"Index",qCode.Index);
        setStemVarInt(rxMsg,options.stem,"Zero",qCode.Zero);
        setStemVarInt(rxMsg,options.stem,"TrackPosition",
            qCode.TrackPosition.LSN);
        setStemVarInt(rxMsg,options.stem,"DiskPosition",
            qCode.DiskPosition.LSN);

    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return */
    return(NULL);

}

STRPTR cdQCodeMSF(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        STRPTR stem;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    STRPTR result=NULL;

    struct QCode qCode;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"STEM/A",&options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Fetch Q-Code */
    cdRequest->io_Command=CD_QCODEMSF;
    cdRequest->io_Length=0;
    cdRequest->io_Data=&qCode;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_QCODEMSF I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        result=NULL;
    } else {

        /* Return QCode data in ARexx stem variable */
        setStemVarInt(rxMsg,options.stem,"CtlAdr",qCode.CtlAdr);
        setStemVarInt(rxMsg,options.stem,"Track",qCode.Track);
        setStemVarInt(rxMsg,options.stem,"Index",qCode.Index);
        setStemVarInt(rxMsg,options.stem,"Zero",qCode.Zero);
        setStemVarInt(rxMsg,options.stem,"TrackPosition.Minute",
            qCode.TrackPosition.MSF.Minute);
        setStemVarInt(rxMsg,options.stem,"TrackPosition.Second",
            qCode.TrackPosition.MSF.Second);
        setStemVarInt(rxMsg,options.stem,"TrackPosition.Frame",
            qCode.TrackPosition.MSF.Frame);
        setStemVarInt(rxMsg,options.stem,"DiskPosition.Minute",
            qCode.DiskPosition.MSF.Minute);
        setStemVarInt(rxMsg,options.stem,"DiskPosition.Second",
            qCode.DiskPosition.MSF.Second);
        setStemVarInt(rxMsg,options.stem,"DiskPosition.Frame",
            qCode.DiskPosition.MSF.Frame);

    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return */
    return(result);

}

STRPTR cdRead(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG *offset;
        LONG *length;
        LONG byteVerify;
        LONG wordVerify;
        LONG offsetVerify;
        LONG *start;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    UBYTE *buffer;

    /* Parse arguments */
    if (debugMode) {
        printf("cdRead: Calling obtainArgs()\n");
    }
    rdArgs=obtainArgs(args,
        "OFFSET/N/A,LENGTH/N/A,BV=BYTEVERIFY/S,WV=WORDVERIFY/S,OV=OFFSETVERIFY/S,START/N",
        &options,sizeof(options));
    if (!rdArgs) {
        if (debugMode) {
            printf("cdRead: obtainArgs() failed\n");
        }
        *error=RC_ERROR;
        return(NULL);
    }
    if (debugMode) {
        printf("cdRead: obtainArgs() good\n");
    }

    /* Allocate buffer */
    buffer=AllocVec(*options.length,NULL);
    if (!buffer) {
        if (debugMode) {
            printf("cdRead: Buffer allocation failed\n");
        }
        *error=RC_ERROR;
        return(NULL);
    }
    if (debugMode) {
        printf("cdRead: Allocated %lu-byte buffer at $%08lx\n",
            *options.length,buffer);
    }

    /* Read data */
    cdRequest->io_Command=CD_READ;
    cdRequest->io_Data=buffer;
    cdRequest->io_Length=*options.length;
    cdRequest->io_Offset=*options.offset;
    if (debugMode) {
        printf("cdRead: Calling CD_READ\n");
    }
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (debugMode) {
        printf("cdRead: CD_READ returned %d\n",ioError);
    }
    if (ioError) {
        if (debugMode) {
            printf("CD_READ I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    } else {
        /* Verify byte pattern */
        if (options.byteVerify) {
            *error=
                byteVerify(buffer,*options.length,
                    options.start?*options.start:0)?
                RC_OK:RC_WARN;
        }
        /* Verify word pattern */
        if (options.wordVerify) {
            *error=
                wordVerify(buffer,*options.length,
                    options.start?*options.start:0)?
                RC_OK:RC_WARN;
        }
        /* Verify offset pattern */
        if (options.offsetVerify) {
            if (debugMode) {
                printf("cdRead: Verifying offset pattern\n");
            }
            *error=
                offsetVerify(buffer,*options.length,
                    options.start?*options.start:0)?
                RC_OK:RC_WARN;
            if (debugMode) {
                printf("cdRead: Offset verify status %d\n",*error);
            }
        }

    }

    /* Free buffer */
    FreeVec(buffer);

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return -- no result */
    return(NULL);

}

STRPTR cdReadXL(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    Printf("%s: Not yet implemented\n",PROGRAM_NAME);

    *error=RC_WARN;
    return(NULL);

}

/****** cd/cdRemChangeInt ******************************************
*
*   NAME
*       cdRemChangeInt -- remove frame interrupt
*
*   SYNOPSIS
*       result=cdRemChangeInt(rxMsg,args,error);
*
*       STRPTR cdRemChangeInt(struct RexxMsg *rxMsg,STRPTR args,LONG *error);
*
*   FUNCTION
*       Remove disk change interrupt.
*
*   INPUTS
*       rxMsg   -   ARexx message
*       args    -   arguments from ARexx
*       error   -   pointer to error value
*
*   RESULT
*       *error  -   Error code (if any)
*       result  -   result string
*
*   EXAMPLE
*
*   NOTES
*
*   BUGS
*       Normal procedure for handling exec.library I/O requests
*       requires that an I/O request be completed before it is
*       re-used. However, for historical reasons, the *_ADDCHANGEINT
*       and *_ADDFRAMEINT commands of trackdisk.device, scsi.device,
*       and cd.device require the caller to send the I/O request
*       to add the interrupt and re-use the I/O request (without
*       calling exec.library/WaitIO()) with a *_REMCHANGEINT
*       or *_REMFRAMEINT command to remove the interrupt.
*
*       This causes IOTorture hits and is, in my humble opinion,
*       a Bad Thing(TM). However, it is apparently well beyond the
*       point where this behavior could be changed.
*
*   SEE ALSO
*       cdAddChangeInt
*
******************************************************************************
*
*/

STRPTR cdRemChangeInt(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    BYTE ioError;

    /* Remove disk change interrupt */
    cdRequest->io_Command=CD_REMCHANGEINT;
    cdRequest->io_Length=sizeof(changeInterrupt);
    cdRequest->io_Data=&changeInterrupt;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_REMCHANGEINT I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    }

    /* Return w/o result */
    return(NULL);

}

/****** cd/cdRemFrameInt ******************************************
*
*   NAME
*       cdRemFrameInt -- remove frame interrupt
*
*   SYNOPSIS
*       result=cdRemFrameInt(rxMsg,args,error);
*
*       STRPTR cdRemFrameInt(struct RexxMsg *rxMsg,STRPTR args,LONG *error);
*
*   FUNCTION
*       Remove frame interrupt.
*
*   INPUTS
*       rxMsg   -   ARexx message
*       args    -   arguments from ARexx
*       error   -   pointer to error value
*
*   RESULT
*       *error  -   Error code (if any)
*       result  -   result string
*
*   EXAMPLE
*
*   NOTES
*
*   BUGS
*       Normal procedure for handling exec.library I/O requests
*       requires that an I/O request be completed before it is
*       re-used. However, for historical reasons, the *_ADDCHANGEINT
*       and *_ADDFRAMEINT commands of trackdisk.device, scsi.device,
*       and cd.device require the caller to send the I/O request
*       to add the interrupt and re-use the I/O request (without
*       calling exec.library/WaitIO()) with a *_REMCHANGEINT
*       or *_REMFRAMEINT command to remove the interrupt.
*
*       This causes IOTorture hits and is, in my humble opinion,
*       a Bad Thing(TM). However, it is apparently well beyond the
*       point where this behavior could be changed.
*
*   SEE ALSO
*       cdAddFrameInt
*
******************************************************************************
*
*/

STRPTR cdRemFrameInt(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    BYTE ioError;

    /* Remove frame interrupt */
    cdRequest->io_Command=CD_REMFRAMEINT;
    cdRequest->io_Length=sizeof(frameInterrupt);
    cdRequest->io_Data=&frameInterrupt;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_REMFRAMEINT I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    }

    /* Return w/o result */
    return(NULL);

}

STRPTR cdSearch(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG normal;
        LONG ffwd;
        LONG frev;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"NORMAL/S,FFWD/S,FREV/S",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Set search mode */
    cdRequest->io_Command=CD_SEARCH;
    if (options.normal) {
        cdRequest->io_Length=CDMODE_NORMAL;
    }
    if (options.ffwd) {
        cdRequest->io_Length=CDMODE_FFWD;
    }
    if (options.frev) {
        cdRequest->io_Length=CDMODE_FREV;
    }
    if (!(options.normal) & !(options.ffwd) & !(options.frev)) {
        cdRequest->io_Length=-1L;
    }
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_SEARCH I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return w/o result */
    return(NULL);

}

STRPTR cdSeek(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        LONG *offset;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"OFFSET/N/A",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    /* Seek */
    cdRequest->io_Command=CD_SEEK;
    cdRequest->io_Offset=*options.offset;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_SEEK I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return w/o result */
    return(NULL);

}

STRPTR cdTOCLSN(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        STRPTR stem;
        LONG *entries;
        LONG *begin;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    struct TOCEntry *buffer;
    struct TOCSummary *summary;
    int index;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"STEM/A,ENTRIES/N/A,BEGIN/N/A",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    if (debugMode) {
        printf("cdTOCLSN -- Stem=%s, Entries=%ld, Begin=%ld\n",
            options.stem,*options.entries,*options.begin);
    }

    /* Allocate TOC buffer */
    buffer=AllocVec(sizeof(struct TOCEntry)*(*options.entries),MEMF_CLEAR);
    if (!buffer) {
        if (debugMode) {
            printf("Error allocating TOC buffer\n");
        }
        *error=RC_ERROR;
        return(NULL);
    }

    /* Fetch TOC in LSN format */
    cdRequest->io_Command=CD_TOCLSN;
    cdRequest->io_Data=buffer;
    cdRequest->io_Length=*options.entries;
    cdRequest->io_Offset=*options.begin;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_TOCLSN I/O error %d",ioError);
        }
        *error=RC_ERROR;
    }
    if (debugMode) {
        printf("CD_TOCLSN good\n");
    }

    /* Return summary, if requested */
    if (*options.begin==0) {

        summary=(struct TOCSummary *) &(buffer[0]);
        setStemVarInt(rxMsg,options.stem,"Summary.FirstTrack",summary->FirstTrack);
        if (debugMode) {
            printf("    FirstTrack=%ld\n",summary->FirstTrack);
        }
        setStemVarInt(rxMsg,options.stem,"Summary.LastTrack",summary->LastTrack);
        if (debugMode) {
            printf("    LastTrack=%ld\n",summary->LastTrack);
        }
        setStemVarInt(rxMsg,options.stem,"Summary.LeadOut",summary->LeadOut.LSN);
        if (debugMode) {
            printf("    LeadOut=%ld\n",summary->LeadOut.LSN);
        }

    }

    /* Return TOC entries */
    for (index=(*options.begin==0)?1:0;
         index<(*options.entries);
         index++) {
        if (debugMode) {
            printf("Entry %d (detail for track %d)\n",index,buffer[index].Track);
        }
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "CtlAdr",buffer[index].CtlAdr);
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "Track",buffer[index].Track);
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "Position",buffer[index].Position.LSN);
    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return w/o result */
    return(NULL);

}

STRPTR cdTOCMSF(struct RexxMsg *rxMsg,STRPTR args,LONG *error)
{

    static struct options {
        STRPTR stem;
        LONG *entries;
        LONG *begin;
    } options;
    struct RDArgs *rdArgs;

    BYTE ioError;

    struct TOCEntry *buffer;
    struct TOCSummary *summary;
    int index;

    /* Parse arguments */
    rdArgs=obtainArgs(args,"STEM/A,ENTRIES/N/A,BEGIN/N/A",
        &options,sizeof(options));
    if (!rdArgs) {
        *error=RC_ERROR;
        return(NULL);
    }

    if (debugMode) {
        printf("cdTOCMSF -- Stem=%s, Entries=%ld, Begin=%ld\n",
            options.stem,*options.entries,*options.begin);
    }

    /* Allocate TOC buffer */
    buffer=AllocVec(sizeof(struct TOCEntry)*(*options.entries),MEMF_CLEAR);
    if (!buffer) {
        if (debugMode) {
            printf("Error allocating TOC buffer\n");
        }
        *error=RC_ERROR;
        return(NULL);
    }

    /* Fetch TOC in MSF format */
    cdRequest->io_Command=CD_TOCMSF;
    cdRequest->io_Data=buffer;
    cdRequest->io_Length=*options.entries;
    cdRequest->io_Offset=*options.begin;
    if (noCD) { ioError=-1; } else { ioError=DoIO(cdRequest); }
    if (ioError) {
        if (debugMode) {
            printf("CD_TOCMSF I/O error %d\n",ioError);
        }
        *error=RC_ERROR;
        *error=RC_ERROR;
    }

    /* Return summary, if requested */
    if (*options.begin==0) {

        summary=(struct TOCSummary *) &(buffer[0]);
        setStemVarInt(rxMsg,options.stem,"Summary.FirstTrack",summary->FirstTrack);
        if (debugMode) {
            printf("    FirstTrack=%ld\n",summary->FirstTrack);
        }
        setStemVarInt(rxMsg,options.stem,"Summary.LastTrack",summary->LastTrack);
        if (debugMode) {
            printf("    LastTrack=%ld\n",summary->LastTrack);
        }
        if (debugMode) {
            printf("    LeadOut=%02d:%02d:%02d\n",
                summary->LeadOut.MSF.Minute,summary->LeadOut.MSF.Second,
                summary->LeadOut.MSF.Frame);
        }
        setStemVarInt(rxMsg,options.stem,"Summary.LeadOut.Minute",
            summary->LeadOut.MSF.Minute);
        setStemVarInt(rxMsg,options.stem,"Summary.LeadOut.Second",
            summary->LeadOut.MSF.Second);
        setStemVarInt(rxMsg,options.stem,"Summary.LeadOut.Frame",
            summary->LeadOut.MSF.Frame);

    }

    /* Return TOC entries */
    for (index=(*options.begin==0)?1:0;
         index<*options.entries;
         index++) {

        if (debugMode) {
            printf("Entry %d (detail for track %d)\n",index,buffer[index].Track);
        }
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "CtlAdr",buffer[index].CtlAdr);
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "Track",buffer[index].Track);
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "Position.Minute",buffer[index].Position.MSF.Minute);
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "Position.Second",buffer[index].Position.MSF.Second);
        setStemVarIntArray(rxMsg,options.stem,buffer[index].Track,
            "Position.Frame",buffer[index].Position.MSF.Frame);

    }

    /* Free argument parsing structure */
    releaseArgs(rdArgs);

    /* Return w/o result */
    return(NULL);

}
