/*
 * Quantis PCI driver
 *
 * Copyright (c) 2004-2010 id Quantique SA, Carouge/Geneva, Switzerland
 * All rights reserved.
 *
 * ----------------------------------------------------------------------------
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY.
 *
 * ----------------------------------------------------------------------------
 *
 * Alternatively, this software may be distributed under the terms of the
 * terms of the GNU General Public License version 2 as published by the
 * Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 * ----------------------------------------------------------------------------
 *
 * For history of changes, ChangeLog.txt
 *
 * This file test the Quantis driver for generating random number.
 *
 Module Name:

    QuantisTest.c  - Test program for the driver

    Refer to the "quantispci_mapping.pdf" document
    for details on these register definitions.

Environment:

    User mode
 */
#include <windows.h>
#include <strsafe.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#pragma warning(disable:4201)  // disable nameless struct/union warnings
#include <winioctl.h>
#include <setupapi.h>
#pragma warning(default:4201)

#include "Public.h"

#define BUFSIZE 64
//Number of random numbers printed on a line of output.
#define NUMBER_PER_LINE 20 

static
ULONG   bufsize = BUFSIZE;


HANDLE
OpenDevice(
    IN CONST GUID * InterfaceGuid,
    IN ULONG        FileFlagOptions
    );
/* Extract all the bytes from a DWORD, starting with the byte of lower weight.
   The function has 2 parameters:
     1) the dword to extract bytes
     2) a pointer on an allocated array of unsigned char of a size >= sizeof(DWORD)

   The function return a pointer on the second parameter if the result is OK and NULL otherwise
*/
static unsigned char * GetDWordBytes(__in DWORD dword, unsigned char * Extracted){
    int i;
    for (i=0;i<sizeof(DWORD);i++){
       Extracted[i]=(unsigned char) (dword >> 8);
       dword >>= 8;
    }
    
    return Extracted;
}   

/*The following function is directly copied from Microsoft documentation. It is
  used to exit the program by giving an error message for the last error
  which has occured. Microsoft gives the following example of use:

  void main()
  {
      // Generate an error

      if(!GetProcessId(NULL))
          ErrorExit(TEXT("GetProcessId"));
  }
*/
void ErrorExit(LPTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message and exit the process

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    ExitProcess(dw); 
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int
__cdecl
main(
    __in int argc,
    __in_ecount(argc) char* argv[]
    )
{
    HANDLE      hPCIdevice;
    PUCHAR      outbuf;
    ULONG       count;
    DWORD       numread;
    OVERLAPPED  ol = {0};
    ULONG       offset;
    ULONG       length;
    DWORD       numwritten;
    BOOL        okay;
    ULONG       i;
    BOOL        hasErrors = FALSE;
    DWORD      IoctlData;
    unsigned char DWordsBytes[80];
    int j;
    
    
    if (argc != 2) {
        bufsize = BUFSIZE;
    } else {
        if (sscanf( argv[1], "%d", &bufsize ) == 0) {
            printf("bad buffer size\n");
            exit(1);
        }
        if (bufsize % sizeof(DWORD)) {

            bufsize += sizeof(DWORD) - (bufsize % sizeof(DWORD));

            printf("Increasing buffer size to %d bytes "
                   "(integral of DWORDs; size of DWORD is %d)\n", bufsize,sizeof(DWORD));
        }
    }
    printf("Buffer size is %d bytes\n", bufsize);

    
    //hPCIdevice = OpenDevice(&GUID_DEVINTERFACE_QUANTIS_PCI,
    //                        FILE_FLAG_OVERLAPPED);
    
    //Not a asynchronous Open.
    hPCIdevice = OpenDevice(&GUID_DEVINTERFACE_QUANTIS_PCI,0);

    if (hPCIdevice == INVALID_HANDLE_VALUE) {
        puts("(PCI) Can't open Quantis device");
        exit(1);
    }

    outbuf = (PUCHAR) malloc(bufsize);
    if (outbuf == NULL) {
        puts("memory allocation for output buffer failed\n");
        exit(1);
    }
    
    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    IoctlData=0;
	
    // Get the number of card
	    puts("Get the number of installed cards.\n");
        okay = DeviceIoControl( hPCIdevice, QUANTIS_IOCTL_GET_CARD_COUNT,
                                NULL,0,
                                &IoctlData,sizeof(DWORD),
                                &numwritten,NULL );
	    printf("The number of bytes written is %d\n",numwritten);
        if (okay){
           printf("There is/are %d Quantis cards installed.\n",IoctlData);
        }
        else{
		  if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
           puts("Ioctl to get the number of card has failed.\n");
        }
    // Get the board version
	    puts("Get the board version.\n");
        okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_GET_BOARD_VERSION,
                                NULL,0,
                                &IoctlData,sizeof(DWORD),
                                &numwritten,NULL );
        if (okay){
           printf("The card version is:0%x.\n", IoctlData);
        }
        else{
	     if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
           puts("Ioctl to get the board version has failed.\n");
        }
    // Get the mask of detected module
	    puts("Get the module mask.\n");
        okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_GET_MODULES_MASK,
                                NULL,0,
                                &IoctlData,sizeof(DWORD),
                                &numwritten,NULL
                                );
        if (okay && numwritten==sizeof(ModuleMask_t)){
           printf("This is the mask of installed modules: %d.\nThis represents %d modules.",
                  IoctlData,
                  GetModule((ModuleMask_t)IoctlData));
        }
        else{
	     if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
		 puts("Ioctl to get the mask of detected modules has failed.\n");
        }
    // Get the module status
	    puts("Get the module status.\n");
        okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_GET_MODULES_STATUS,
                                NULL,0,
                                &IoctlData,sizeof(DWORD),
                                &numwritten,NULL );
        if (okay && numwritten==sizeof(ModuleMask_t)){
           printf("This is the status of installed modules: %d. (currently identical to mask of module)\n",(ModuleMask_t)IoctlData);
        }
        else{
	     if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
		 puts("Ioctl to get the module status has failed.\n");
        }
    // Get the driver version
	    puts("Get the driver version.\n");
        okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_GET_DRIVER_VERSION,
                                NULL,0,
                                &IoctlData,sizeof(DWORD),
                                &numwritten,NULL );
        if (okay){
		 printf("The version of the driver is %d.\n",IoctlData);
        }
        else{
	     if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
		 puts("Ioctl to get driver version has failed.\n");
        }
    // Disable Module 1
	    puts("Disable first module of the card.\n");
        IoctlData=MODULE1;
        okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_DISABLE_MODULE,
                                &IoctlData,sizeof(DWORD),
                                NULL,0,
                                &numwritten,NULL );
        if (okay)
        { // Get the module status
          okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_GET_MODULES_STATUS,
                                  NULL,0,
                                  &IoctlData,sizeof(DWORD),
                                  &numwritten,NULL );
          if (okay && numwritten==sizeof(ModuleMask_t)){
             printf("This is the status of installed modules: %d. (currently identical to mask of module)\n",
                    (ModuleMask_t)IoctlData);
          }
        }
        else{
		  if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
          puts("Ioctl to disable module has failed.\n");
        }
    // Enable Module 1
	    puts("Enable the first module of the card.\n");
        IoctlData=MODULE1;
        okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_ENABLE_MODULE,
                                &IoctlData,sizeof(DWORD),
                                NULL,0,
                                &numwritten,NULL );
        if (okay)
        { // Get the module status
          okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_GET_MODULES_STATUS,
                                  NULL,0,
                                  &IoctlData,sizeof(DWORD),
                                  &numwritten,NULL );
          if (okay && numwritten==sizeof(ModuleMask_t)){
             printf("This is the status of installed modules: %d. (currently identical to mask of module)\n",
                    (ModuleMask_t)IoctlData);
          }
        }
        else{
		  if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
          puts("Ioctl to enable module has failed.\n");
        }
    // Reset the board
	    puts("Reset the board.\n");
        okay = DeviceIoControl( hPCIdevice,QUANTIS_IOCTL_RESET_BOARD,
                                NULL,0,
                                NULL,0,
                                &numwritten,NULL );
        if (okay){ puts("The quantis board has been reset.\n");}
        else { 
		  if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
	      puts("resetting the Quantis board has failed.\n");
	    }

        puts("Read random data from the card.\n");
    okay = ReadFile( hPCIdevice,outbuf,bufsize,&numread,NULL );

   if (okay){
      i=0;
      printf("Here are %d random number:\n",numread);
      while (i<numread){
          printf("Number %d=%x\n",i,outbuf[i]);
          i++;
      }
    }
    else{
	   if(!GetProcessId(NULL)){ErrorExit(TEXT("GetProcessId"));}
       puts("An error has occured while obtaining random numbers\n");
    }
        


    CloseHandle(ol.hEvent);
    free(outbuf);
    CloseHandle(hPCIdevice);

    exit(0);
}

HANDLE
OpenDevice(
    IN CONST GUID * InterfaceGuid,
    IN ULONG        FileFlagOptions
    )
{
    SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
    SP_DEVINFO_DATA DeviceInfoData;
    HDEVINFO hDevInfo;
    PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceInterfaceDetail = NULL;
    HANDLE hDevice;

    ULONG size;
    int count, i, index;
    BOOL status = TRUE;
    TCHAR *DeviceName = NULL;
    TCHAR *DeviceLocation = NULL;

    //
    //  Retreive the device information for all PLX devices.
    //
    hDevInfo =     SetupDiGetClassDevs(InterfaceGuid,
                                                NULL,
                                                NULL,
                                                DIGCF_DEVICEINTERFACE |
                                                DIGCF_PRESENT );
    if(INVALID_HANDLE_VALUE == hDevInfo) {
        printf("No Quantis devices are present and enabled in the system\n");
        return INVALID_HANDLE_VALUE;
    }
    //
    //  Initialize the SP_DEVICE_INTERFACE_DATA Structure.
    //
    DeviceInterfaceData.cbSize  = sizeof(SP_DEVICE_INTERFACE_DATA);

    //
    //  Determine how many devices are present.
    //
    count = 0;
    while(SetupDiEnumDeviceInterfaces(hDevInfo,
                                    NULL,
                                    InterfaceGuid,
                                    count++,  //Cycle through the available devices.
                                    &DeviceInterfaceData));

    //
    // Since the last call fails when all devices have been enumerated,
    // decrement the count to get the true device count.
    //
    count--;

    //
    //  If the count is zero then there are no devices present.
    //
    if(count == 0)
    {
        printf("No Quantis devices are present and enabled in the system.\n");
        goto End;
    }

    //
    //  Initialize the appropriate data structures in preparation for
    //  the SetupDi calls.
    //
    DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);


    //
    //  Loop through the device list to allow user to choose
    //  a device.  If there is only one device, select it
    //  by default.
    //
    i = 0;
    while(SetupDiEnumDeviceInterfaces(hDevInfo,
                                NULL,
                                (LPGUID)InterfaceGuid,
                                i,
                                &DeviceInterfaceData))
    {

        //
        // Determine the size required for the DeviceInterfaceData
        //
        SetupDiGetDeviceInterfaceDetail(hDevInfo,
                                        &DeviceInterfaceData,
                                        NULL,
                                        0,
                                        &size,
                                        NULL);

        if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            printf("SetupDiGetDeviceInterfaceDetail failed, Error: %d", GetLastError());
            goto End;
        }
        pDeviceInterfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(size);
        if(!pDeviceInterfaceDetail)
        {
            printf("Insufficient memory.\n");
            goto End;
        }

        //
        // Initialize structure and retrieve data.
        //
        pDeviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        status = SetupDiGetDeviceInterfaceDetail(hDevInfo,
                                    &DeviceInterfaceData,
                                    pDeviceInterfaceDetail,
                                    size,
                                    NULL,
                                    &DeviceInfoData);

        free(pDeviceInterfaceDetail);
        pDeviceInterfaceDetail = NULL;

        if(!status)
        {
            printf("SetupDiGetDeviceInterfaceDetail failed, Error: %d", GetLastError());
            goto End;
        }

        //
        //  Get the Device Name
        //  Calls to SetupDiGetDeviceRegistryProperty require two consecutive
        //  calls, first to get required buffer size and second to get
        //  the data.
        //
        SetupDiGetDeviceRegistryProperty(hDevInfo,
                                        &DeviceInfoData,
                                        SPDRP_DEVICEDESC,
                                        NULL,
                                        (PBYTE)DeviceName,
                                        0,
                                        &size);

        if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
        {
            printf("SetupDiGetDeviceRegistryProperty failed, Error: %d", GetLastError());
            goto End;
        }

        DeviceName = (TCHAR*) malloc(size);
        if(!DeviceName)
        {
            printf("Insufficient memory.\n");
            goto End;
        }

        status = SetupDiGetDeviceRegistryProperty(hDevInfo,
                                        &DeviceInfoData,
                                        SPDRP_DEVICEDESC,
                                        NULL,
                                        (PBYTE)DeviceName,
                                        size,
                                        NULL);
        if(!status)
        {
            printf("SetupDiGetDeviceRegistryProperty failed, Error: %d", GetLastError());
            free(DeviceName);
            goto End;
        }

        //
        //  Now retrieve the Device Location.
        //
        SetupDiGetDeviceRegistryProperty(hDevInfo,
                                        &DeviceInfoData,
                                        SPDRP_LOCATION_INFORMATION,
                                        NULL,
                                        (PBYTE)DeviceLocation,
                                        0,
                                        &size);

        if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            DeviceLocation = (TCHAR*) malloc(size);

            if (DeviceLocation != NULL)
            {
                status = SetupDiGetDeviceRegistryProperty(hDevInfo,
                                                &DeviceInfoData,
                                                SPDRP_LOCATION_INFORMATION,
                                                NULL,
                                                (PBYTE)DeviceLocation,
                                                size,
                                                NULL);
                if(!status)
                {
                    free(DeviceLocation);
                    DeviceLocation = NULL;
                }
            }

        } else {
            DeviceLocation = NULL;
        }

        //
        // If there is more than one device print description.
        //
        if(count > 1)
        {
            printf("%d- ", i);
        }

        printf("DeviceName: \"%s\"\n", DeviceName);

        if(DeviceLocation)
        {
            printf("        \"%s\" location\n", DeviceLocation);
        }

        free(DeviceName);
        free(DeviceLocation);

        i++; // Cycle through the available devices.
    }

    //
    //  Select device.
    //
    index = 0;
    if(count > 1)
    {
        printf("\nSelect Device: ");
        (void)scanf_s("%d", &index);
    }

    //
    //  Get information for specific device.
    //

    status = SetupDiEnumDeviceInterfaces(hDevInfo,
                                    NULL,
                                    (LPGUID)InterfaceGuid,
                                    index,
                                    &DeviceInterfaceData);

    if(!status)
    {
        printf("SetupDiEnumDeviceInterfaces failed, Error: %d", GetLastError());
        goto End;
    }

    //
    // Determine the size required for the DeviceInterfaceData
    //
    SetupDiGetDeviceInterfaceDetail(hDevInfo,
                                    &DeviceInterfaceData,
                                    NULL,
                                    0,
                                    &size,
                                    NULL);

    if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    {
        printf("SetupDiGetDeviceInterfaceDetail failed, Error: %d", GetLastError());
        goto End;
    }

    pDeviceInterfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(size);
    if(!pDeviceInterfaceDetail)
    {
        printf("Insufficient memory.\n");
        goto End;
    }

    //
    // Initialize structure and retrieve data.
    //
    pDeviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
    status = SetupDiGetDeviceInterfaceDetail(hDevInfo,
                                &DeviceInterfaceData,
                                pDeviceInterfaceDetail,
                                size,
                                NULL,
                                &DeviceInfoData);
    if(!status)
    {
        printf("SetupDiGetDeviceInterfaceDetail failed, Error: %d", GetLastError());
        goto End;
    }

    //
    //  Get handle to device.
    //
    hDevice = CreateFile(pDeviceInterfaceDetail->DevicePath,
                             GENERIC_READ | GENERIC_WRITE,
                             0,
                             NULL,
                             OPEN_EXISTING,
                             FileFlagOptions,
                             NULL );

    if(hDevice == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile failed.  Error:%d", GetLastError());
    }

    free(pDeviceInterfaceDetail);
    SetupDiDestroyDeviceInfoList(hDevInfo);

    return hDevice;

End:
    if(pDeviceInterfaceDetail) {
        free(pDeviceInterfaceDetail);
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
    return INVALID_HANDLE_VALUE;
}
