/*
 * OS2CDROM.DMD interface for sending SCSI commands
 *
 * Based on example code by:
 *	(c) 2001 S&T Systemtechnik GmbH					                    
 *									                                    
 *	erzeugt:	15.02.01	R.Ihle				                        
 *
 * Project Odin Software License can be found in LICENSE.TXT
 *
 */

#define	INCL_NOPMAPI
#define	INCL_DOSDEVICES
#define	INCL_DOSDEVIOCTL
#define	INCL_DOSFILEMGR
#define	INCL_DOSMISC
#include <os2wrap.h>

#include "cdioctl.h"

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

#include "cdio.h"
#include <win32type.h>
#include <dbglog.h>


//needed for high memory support check (kernel32)
DWORD SYSTEM OSLibDosDevIOCtl( DWORD hFile, DWORD dwCat, DWORD dwFunc,
                               PVOID pParm, DWORD dwParmMaxLen, DWORD *pdwParmLen,
                               PVOID pData, DWORD dwDataMaxLen, DWORD *pdwDataLen);


static ULONG ulDriveList            = 0;
static ULONG nrCDROMDrives          = 0;
static HCDIO hCDHandles[MAX_DRIVES] = {0};
static int   ulCDDriveNr[MAX_DRIVES] = {0};

//----------------------------------------------------------------------//
//	Initialize CD/DVD drive access					//
//----------------------------------------------------------------------//

BOOL OSLibCdIoInitialize(void)
{
    ULONG ulAction, ulFeatures;
    int	  fResult = 0;
    HFILE hDev;

    if( DosOpen("CD-ROM2$", &hDev, &ulAction, 0, FILE_NORMAL,
	            OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
	            OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE |
	            OPEN_FLAGS_FAIL_ON_ERROR, NULL) == 0 )
    {
        fResult = DosDevIOCtl(hDev, IOCTL_CDROMDISK2, CDROMDISK2_FEATURES,
			                  NULL, 0, NULL, &ulFeatures, sizeof(ulFeatures), NULL) == 0 &&
	              (ulFeatures & FEATURE_EXECMD_SUPPORT);

        DosClose(hDev);
    }

    ulDriveList = OSLibCdIoQueryDriveList();
    return fResult;
}


//----------------------------------------------------------------------//
//	Unload ASPI manager						//
//----------------------------------------------------------------------//

BOOL OSLibCdIoTerminate(void)
{
    for(int i=0;i<MAX_DRIVES;i++) {
        if(hCDHandles[i]) {
            DosClose(hCDHandles[i]);
            hCDHandles[i] = 0;
        }
    }
    return 1;
}


//----------------------------------------------------------------------//
//	Open drive							//
//----------------------------------------------------------------------//

BOOL OSLibCdIoIsSupported(HFILE hDisk)
{
    ULONG ulAction, ulCDSig;

    ulCDSig = ('C') | ('D' << 8) | ('9' << 16) | ('9' << 24);

    if(OSLibCdIoInitialize() == FALSE) {
        return FALSE;
    }

    if(DosDevIOCtl(hDisk, IOCTL_CDROMDISK, CDROMDISK_GETDRIVER, &ulCDSig, sizeof(ulCDSig), NULL,
		           &ulCDSig, sizeof(ulCDSig), NULL) == 0 &&
	    ulCDSig == (('C') | ('D' << 8) | ('0' << 16) | ('1' << 24)) )
    {
        return TRUE;
    }
    return FALSE;
}

//----------------------------------------------------------------------//
//	Return nr of available CDROM or DVD drives
//----------------------------------------------------------------------//
int OSLibCdIoGetNumDrives()
{
    return min(nrCDROMDrives, MAX_DRIVES);
}

//----------------------------------------------------------------------//
//	Open cdrom drive
//----------------------------------------------------------------------//

HCDIO OSLibCdIoGetDevice(int SRB_HaId, int SRB_Target, int SRB_Lun)
{
    HFILE		hDisk;
    ULONG		ulAction, ulCDSig;
    char        pszDisk[4];

    if(SRB_HaId != 0 && SRB_Target != 0 || SRB_Lun > (nrCDROMDrives - 1)) {
        return (HCDIO) -1;
    }
    
    if(hCDHandles[SRB_Lun]) {
        return hCDHandles[SRB_Lun];
    }
    pszDisk[0] = 'A' + ulCDDriveNr[SRB_Lun];
    pszDisk[1] = ':';
    pszDisk[2] = 0;

    //?? readwrite for cd writers?
    if( DosOpen(pszDisk, &hDisk,
	            &ulAction, 0, FILE_NORMAL,
	            OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
	            OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE |
	            OPEN_FLAGS_DASD | OPEN_FLAGS_FAIL_ON_ERROR, NULL) == 0 )
    {
        ulCDSig = ('C') | ('D' << 8) | ('9' << 16) | ('9' << 24);

        if( DosDevIOCtl(hDisk, IOCTL_CDROMDISK, CDROMDISK_GETDRIVER,
		                &ulCDSig, sizeof(ulCDSig), NULL,
		                &ulCDSig, sizeof(ulCDSig), NULL) == 0 &&
	        ulCDSig == (('C') | ('D' << 8) | ('0' << 16) | ('1' << 24)) )
        {
            hCDHandles[SRB_Lun] = hDisk;
            return (HCDIO)hDisk;
        }
    
        DosClose(hDisk);
    }
    return (HCDIO)-1;
}

//----------------------------------------------------------------------//
//	Send command to drive						//
//----------------------------------------------------------------------//

BOOL OSLibCdIoSendCommand(HCDIO hCdIo, CDIO_CMD_BUFFER *pCmdBuf, void *pData, unsigned cbData)
{
    struct ExecCmd	Param;
    ULONG			ulDummy, ParmLen, DataLen;

    if( !pData || !cbData )
    {
        ulDummy = 0;  pData = &ulDummy;  cbData = sizeof(ulDummy);
    }

    Param.ID_code     = 'C' | ('D'<<8) | ('0'<<16) | ('1'<<24);
    Param.data_length = (USHORT)cbData;
    Param.cmd_length  = pCmdBuf->cbCDB;
    Param.flags       = (USHORT)
		      (( pCmdBuf->flDirection & 1 ) ? EX_DIRECTION_IN : 0);
    memcpy(Param.cmd_buffer, pCmdBuf->arCDB, sizeof(Param.cmd_buffer));


    ParmLen = sizeof(Param);
    DataLen = cbData;
    return OSLibDosDevIOCtl((HFILE)hCdIo, IOCTL_CDROMDISK, CDROMDISK_EXECMD,
		                     &Param, ParmLen, &ParmLen, pData, cbData, &DataLen) == 0;
}


//----------------------------------------------------------------------//
//	Load / Eject disk						//
//----------------------------------------------------------------------//

BOOL OSLibCdIoLoadEjectDisk(HCDIO hCdIo, int fLoad)
{
    CDIO_CMD_BUFFER	CmdBuf;

    memset(&CmdBuf, 0, sizeof(CmdBuf));
    CmdBuf.flDirection = CMDDIR_OUTPUT;
    CmdBuf.cbCDB       = 6;
    CmdBuf.arCDB[0]    = 0x1b;		// START STOP UNIT command
    CmdBuf.arCDB[4]    = (unsigned char)(( fLoad ) ?  0x03 : 0x02);
					    // Bit 1: 0:Start/Stop, 1:Load/Eject
					    // Bit 0: 0:Stop/Eject, 1:Start/Load
    return OSLibCdIoSendCommand(hCdIo, &CmdBuf, NULL, 0);
}


//----------------------------------------------------------------------//
//	Test drive readyness						//
//----------------------------------------------------------------------//

BOOL OSLibCdIoUnitReady(HCDIO hCdIo)
{
    CDIO_CMD_BUFFER	CmdBuf;

    memset(&CmdBuf, 0, sizeof(CmdBuf));
    CmdBuf.flDirection = CMDDIR_OUTPUT;
    CmdBuf.cbCDB       = 6;
//  CmdBuf.arCDB[0]    = 0x00;		// TEST UNIT READY command

    return OSLibCdIoSendCommand(hCdIo, &CmdBuf, NULL, 0);
}


//----------------------------------------------------------------------//
//	Reset drive							//
//----------------------------------------------------------------------//

BOOL OSLibCdIoResetUnit(HCDIO hCdIo)
{
    CDIO_CMD_BUFFER	CmdBuf;

    memset(&CmdBuf, 0, sizeof(CmdBuf));
    CmdBuf.flDirection = CMDDIR_OUTPUT;
    CmdBuf.cbCDB       = 6;
    CmdBuf.arCDB[0]    = 0x01;		// REZERO command

    return OSLibCdIoSendCommand(hCdIo, &CmdBuf, NULL, 0);
}


//----------------------------------------------------------------------//
//	Query information about drive					//
//----------------------------------------------------------------------//

BOOL OSLibCdIoInquiry(HCDIO hCdIo, unsigned uPageCode, void *pData, unsigned cbData)
{
    CDIO_CMD_BUFFER	CmdBuf;

    if( cbData > 255 )	cbData = 255;

    memset(&CmdBuf, 0, sizeof(CmdBuf));
    CmdBuf.flDirection = CMDDIR_INPUT;
    CmdBuf.cbCDB       = 6;
    CmdBuf.arCDB[0]    = 0x12;		// INQUIRY CAPACITY command
    CmdBuf.arCDB[4]    = (unsigned char)cbData;

    if( uPageCode )
    {
        CmdBuf.arCDB[1]  = 1;		// enable vital product data
        CmdBuf.arCDB[2]  = (unsigned char)uPageCode;
    }

    return OSLibCdIoSendCommand(hCdIo, &CmdBuf, pData, cbData);
}


//----------------------------------------------------------------------//
//	Query sense information						//
//----------------------------------------------------------------------//

BOOL OSLibCdIoRequestSense(HCDIO hCdIo, void *pData, unsigned cbData)
{
    CDIO_CMD_BUFFER	CmdBuf;

    if( cbData > 255 )	cbData = 255;

    memset(&CmdBuf, 0, sizeof(CmdBuf));
    CmdBuf.flDirection = CMDDIR_INPUT;
    CmdBuf.cbCDB       = 6;
    CmdBuf.arCDB[0]    = 0x03;		// REQUEST SENSE command
    CmdBuf.arCDB[4]    = (unsigned char)cbData;

    return OSLibCdIoSendCommand(hCdIo, &CmdBuf, pData, cbData);
}


//----------------------------------------------------------------------//
//	Query disc capacity						//
//----------------------------------------------------------------------//

BOOL OSLibCdIoQueryCapacity(HCDIO hCdIo, unsigned *pctBlocks, unsigned *pcbBlock)
{
    CDIO_CMD_BUFFER	CmdBuf;
    unsigned		arResult[2];

    memset(&CmdBuf, 0, sizeof(CmdBuf));
    CmdBuf.flDirection = CMDDIR_INPUT;
    CmdBuf.cbCDB       = 10;
    CmdBuf.arCDB[0]    = 0x25;		// READ CAPACITY command

    if( OSLibCdIoSendCommand(hCdIo, &CmdBuf, arResult, sizeof(arResult)) )
    {
        *pctBlocks = (arResult[0] << 24) |
		    ((arResult[0] & 0x0000ff00) << 8) |
		    ((arResult[0] & 0x00ff0000) >> 8) |
		    (arResult[0] >> 24);
    
        *pcbBlock =  (arResult[1] << 24) |
		    ((arResult[1] & 0x0000ff00) << 8) |
		    ((arResult[1] & 0x00ff0000) >> 8) |
		    (arResult[1] >> 24);

        return 1;
    }

    return 0;
}


//----------------------------------------------------------------------//
//	Read data							//
//----------------------------------------------------------------------//

BOOL OSLibCdIoReadBlock(HCDIO hCdIo, unsigned uLBA, void *pData, unsigned cbData)
{
    CDIO_CMD_BUFFER	CmdBuf;

    memset(&CmdBuf, 0, sizeof(CmdBuf));
    CmdBuf.flDirection = CMDDIR_INPUT;
    CmdBuf.cbCDB       = 10;
//  CmdBuf.arCDB[0]    = 0x3e;		// READ LONG command MODE SELECT ???
//  CmdBuf.arCDB[1]    = 0x02;		// Bit 1: perform ECC

    CmdBuf.arCDB[0]    = 0x28;		// READ command
    CmdBuf.arCDB[2]    = (unsigned char)(uLBA >> 24);
    CmdBuf.arCDB[3]    = (unsigned char)(uLBA >> 16);
    CmdBuf.arCDB[4]    = (unsigned char)(uLBA >> 8);
    CmdBuf.arCDB[5]    = (unsigned char)(uLBA);
    CmdBuf.arCDB[7]    = 0;
    CmdBuf.arCDB[8]    = 1;

    return ( cbData > 2047 ) ? OSLibCdIoSendCommand(hCdIo, &CmdBuf, pData, cbData) : 0;
}


//----------------------------------------------------------------------//
//	Query bit mask of all CD drives					//
//----------------------------------------------------------------------//

ULONG OSLibCdIoQueryDriveList(void)
{
    BIOSPARAMETERBLOCK    BPB;
    HFILE			hDev;
    struct DrvInfo	CDInfo;
    ULONG			ulAction, ulParam, uDriveMap = 0;

    if( DosOpen("CD-ROM2$", &hDev, &ulAction, 0, FILE_NORMAL,
	        OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW,
	        OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE |
	        OPEN_FLAGS_FAIL_ON_ERROR, NULL) == 0 )
    {
        if( DosDevIOCtl(hDev, IOCTL_CDROMDISK2, CDROMDISK2_DRVINFO,
		        NULL, 0, NULL, &CDInfo, sizeof(CDInfo), NULL) == 0 )
        {
        // Workaround for Warp4/FP14 kernel bug ...
        ulParam = CDInfo.usFirstDrvNo << 8;
        while( CDInfo.usFirstDrvNo <= ('Z' - 'A') &&
	        DosDevIOCtl((HFILE)-1, IOCTL_DISK, DSK_GETDEVICEPARAMS,
			            &ulParam, 2, NULL, &BPB, sizeof(BPB), NULL) == 0 &&
			            BPB.bDeviceType != DEVTYPE_UNKNOWN )
        {
	            ulParam = ++CDInfo.usFirstDrvNo;
        }

        if( CDInfo.usFirstDrvNo > ('Z' - 'A') )	CDInfo.usDrvCount = 0;

        nrCDROMDrives = CDInfo.usDrvCount;

        while( CDInfo.usDrvCount-- )
            ulCDDriveNr[CDInfo.usDrvCount] = CDInfo.usFirstDrvNo + CDInfo.usDrvCount;
	        uDriveMap |= 1 << (CDInfo.usFirstDrvNo + CDInfo.usDrvCount);
        }

        DosClose(hDev);
    }
    
    return uDriveMap;
}


//----------------------------------------------------------------------//
//	Query fully qualified path name of a given file			//
//----------------------------------------------------------------------//

BOOL OSLibCdIoQueryFullName(char *pchPartName, char *pchFullName, unsigned cbFullName)
{
    APIRET	rc;

    DosError(FERR_DISABLEHARDERR);
    rc = DosQueryPathInfo(pchPartName, FIL_QUERYFULLNAME, pchFullName, cbFullName);
    DosError(FERR_ENABLEHARDERR | FERR_ENABLEEXCEPTION);

    return rc == 0;
}


//----------------------------------------------------------------------//
//	Read volume label		 				//
//----------------------------------------------------------------------//
BOOL OSLibCdIoQueryVolumeLabel(char chDrive, char *pchLabel, unsigned cbLabel)
{
    FSINFO	FsInfo;

    memset(pchLabel, '\0', cbLabel);
    if( cbLabel && DosQueryFSInfo((chDrive & 0x5f) - 'A' + 1,
				    FSIL_VOLSER, &FsInfo, sizeof(FsInfo)) == 0 )
    {
        strncpy(pchLabel, FsInfo.vol.szVolLabel, cbLabel);
        pchLabel[cbLabel-1] = '\0';
        return 1;
    }

    return 0;
}


