//---------------------------------------------------------------------------
// Copyright (C) 2007 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//---------------------------------------------------------------------------
//
//  winusbses.c - Acquire and release a Session on the 1-Wire Net using
//                Microsoft's WinUSB to interface to DS2490-based adapters.
//
//  Version: 3.00
//
//  History:
//

#include "ownet.h"
#include "winusbds2490.h"

// local functions
static SMALLINT DS2490Discover(void);

// device handles and device paths to USB ports stored in deviceList
DeviceData deviceList[50];

// only one handle is available for WinUSB
WINUSB_INTERFACE_HANDLE winusbHandle;
// variable to determine if 2490Discover has been called
static SMALLINT usbhnd_init = 0;

//---------------------------------------------------------------------------
// Attempt to acquire a 1-Wire net using a USB port and a DS2490 based
// adapter.
//
// 'portnum'    - number 0 to MAX_PORTNUM-1.  This number is provided to
//                indicate the symbolic port number.
// 'port_zstr'  - zero terminated port name.  For this platform
//                use format '\\.\DS2490-X' where X is the port number.
//
// Returns: TRUE - success, COM port opened
//
SMALLINT owAcquire(int portnum, char *port_zstr)
{
	int retportnum = 0;
	retportnum = owAcquireEx(port_zstr);
	if ((retportnum < 0) || (retportnum != portnum))
	{
		// Failure from owAcquireEx or from not matching port numbers.
		return FALSE;
	}
	return TRUE;
}

//---------------------------------------------------------------------------
// Attempt to acquire the specified 1-Wire net.
//
// 'port_zstr'     - zero terminated port name.
//
// Returns: port number or -1 if not successful in setting up the port.
//
int owAcquireEx(char *port_zstr)
{
   int portnum = 0, portstringlength = 0, i = 0, ret = 0;
   int portnumfromstring; //character escape sequence
   char portSubStr[] = "DS2490-";
   char *retStr;
   char tempStr[4];
   BOOL nStatus = FALSE;
   SMALLINT numofdevicesfound = 0;
   WINUSB_PIPE_INFORMATION pipe;
   ULONG timeout;
   ULONG sztimeout = 0;

   sztimeout = sizeof(ULONG);

   // initialize tempStr
   memset(&tempStr[0],0x00,4);

   // convert supplied string to uppercase
   for (i = 0;i < (int)strlen(port_zstr); i++)
   {
      port_zstr[i] = (char)toupper((char)port_zstr[i]); 
   }

   // strip off portnumber from supplied string and put it in tempStr
   portstringlength = strlen(port_zstr) - strlen(&portSubStr[0]); // get port string length
   if ((portstringlength < 0) || (portstringlength > 3))
   {
	   // error portstring length can only be 3 characters (i.e., "0" to "999")
      OWERROR(OWERROR_PORTNUM_ERROR);
      return -1;
   }
   for (i=0;i<portstringlength;i++) // tempStr holds the "stripped" port number (as a string)
   {
      tempStr[i] = port_zstr[i + 7];
	}

   // check the port string and grab the port number from it.
   retStr = strstr (port_zstr, &portSubStr[0]); // check for "DS2490-"
   if (retStr == NULL)
   {
      // error - owAcquire called with invalid port string
      OWERROR(OWERROR_PORTNUM_ERROR); 
      return -1;
   }
   portnumfromstring = atoi(&tempStr[0]);
	
   if (portnumfromstring == 0) // port could be zero, so make an extra check befor failing
   {
      ret = memcmp(&tempStr[0],"0",1);
      if (ret != 0) 
      {
         // error - portnum supplied is not numeric
         OWERROR(OWERROR_PORTNUM_ERROR);
         return -1;
      }
   }

   // set portnum to the one provided
   portnum = portnumfromstring;

   if(!usbhnd_init)  // check to see the USB bus has been setup properly
   {
      // discover all DS2490s on USB bus...
	   numofdevicesfound = DS2490Discover(); // Called to find device interface paths
                                            // and put them in deviceList array
      usbhnd_init = 1;
   }

   if (portnum > (numofdevicesfound - 1))
   {
      // ERROR, so close handles and free system resources
      OWERROR(OWERROR_GET_SYSTEM_RESOURCE_FAILED);

      // free the deviceDetailData generated from DS2490Discover
      for (i = 0; i < numofdevicesfound; i++)
         free(deviceList[i].deviceDetailData);

      usbhnd_init = 0;
      return -1;
   }

   // Create a handle to device interface
   deviceList[portnum].deviceInterfaceHandle = CreateFile (deviceList[portnum].deviceDetailData->DevicePath,
                                                GENERIC_READ | GENERIC_WRITE,
                                                FILE_SHARE_READ |
                                                FILE_SHARE_WRITE,
                                                NULL,
                                                OPEN_EXISTING,
                                                FILE_FLAG_OVERLAPPED,
                                                NULL);

   if (deviceList[portnum].deviceInterfaceHandle != INVALID_HANDLE_VALUE) 
   { 
      // successfully obtained device interface handle
      
      // now, get handle to WinUSB
      nStatus = WinUsb_Initialize (deviceList[portnum].deviceInterfaceHandle, &winusbHandle);
      if (nStatus == FALSE) 
      { 
         // ERROR, so close handles and free system resources
         WinUsb_Free(winusbHandle);
         CloseHandle(deviceList[portnum].deviceInterfaceHandle);
         OWERROR(OWERROR_GET_SYSTEM_RESOURCE_FAILED);

         // free the deviceDetailData generated from DS2490Discover
         for (i = 0; i < numofdevicesfound; i++)
            free(deviceList[i].deviceDetailData);

         usbhnd_init = 0;
         return -1;
      }

      // verify adapter is working
      if (!AdapterRecover(portnum))
      {
         // ERROR, so close handles and free system resources
         WinUsb_Free(winusbHandle);
         CloseHandle(deviceList[portnum].deviceInterfaceHandle);
         OWERROR(OWERROR_GET_SYSTEM_RESOURCE_FAILED);
         usbhnd_init = 0;
         // "free" winusb handle
         ////WinUsb_Free(winusbHandle);
         // free the deviceDetailData generated from DS2490Discover
         for (i = 0; i < numofdevicesfound; i++)
            free(deviceList[i].deviceDetailData);

         return -1;
      }
      // set WinUSB pipe policies for End Point 3 (Bulk read)
      // get a winusb pipe 2 (which should be equivalent to DS2490_EP3), so we can set the pipe policies.
      if (!WinUsb_QueryPipe (winusbHandle, 0, 2, &pipe))
      {
	      // Error
	      OWERROR(OWERROR_ADAPTER_ERROR);
	      return -1;
      }		
      if (WinUsb_GetPipePolicy(winusbHandle, pipe.PipeId, PIPE_TRANSFER_TIMEOUT,&sztimeout,&timeout))
      {
	      timeout = 500; // set to 500ms
	      WinUsb_SetPipePolicy(winusbHandle, pipe.PipeId, PIPE_TRANSFER_TIMEOUT,sizeof(ULONG),&timeout); // set timeout
      }

      // reset the 1-Wire
      owTouchReset(portnum);
   }
   else
   {
      // ERROR, so close handles and free system resources
      CloseHandle(deviceList[portnum].deviceInterfaceHandle);
      OWERROR(OWERROR_GET_SYSTEM_RESOURCE_FAILED);
      // free the deviceDetailData generated from DS2490Discover
      for (i = 0; i < numofdevicesfound; i++)
         free(deviceList[i].deviceDetailData);
      usbhnd_init = 0;
      return -1;
   }

   // free the deviceDetailData generated from DS2490Discover
   for (i = 0; i < numofdevicesfound; i++)
      free(deviceList[i].deviceDetailData);

   return portnum;
}

//---------------------------------------------------------------------------
// Release the port that previously acquired a 1-Wire net.
//
// 'portnum'    - number 0 to MAX_PORTNUM-1.  This number is provided to
//                indicate the symbolic port number.
//
void owRelease(int portnum)
{
   WinUsb_Free(winusbHandle);
   CloseHandle(deviceList[portnum].deviceInterfaceHandle);
   // "free" winusb handle
   usbhnd_init = 0;
   winusbHandle = 0;
}

//---------------------------------------------------------------------------
// Discover all DS2490s on bus and put their device paths into deviceList 
// (a global array)
//
// Returns:  Number of DS2490s found on bus
//
SMALLINT DS2490Discover(void)
{
   HDEVINFO                            hdevClassInfo;   // A handle to a device information set.
                                                        // Requires passing in GUID of class info from
                                                        // inf file used to install WinUSB.
   SP_DEVICE_INTERFACE_DATA            deviceInfoData;
   ULONG                               index, i;
   ULONG                               requiredLength;
   BOOL                                nStatus = FALSE;

   // Iterate over usb devices using the new GUID based interface

   // First get handle to device class info associated with the DS9490's GUID 
   // registered to WinUSB through the INF installation file.  It is also 
   // located in the FindDS2490s.h file.
   hdevClassInfo = SetupDiGetClassDevs((LPGUID)&GUID_DEVINTERFACE_DS2490,
                                       NULL,
                                       NULL,
                                       (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

   // If handle is invalid then return error
   if (hdevClassInfo == INVALID_HANDLE_VALUE)
   {
      //ERROR 
      return (SMALLINT) 0;
   }

   deviceInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

   // Iteratively call SetupDiEnumDeviceInterfaces to determine how many elements are in the device 
   // information set.  The number of elements found will be held in "index".  For our case, this 
   // will signify the number of DS2490-based devices on the USB bus.
   for (index = 0; TRUE; index++) 
   {
      nStatus = SetupDiEnumDeviceInterfaces (hdevClassInfo, 
                                             NULL, 
                                             (LPGUID)&GUID_DEVINTERFACE_DS2490, 
                                             index, 
                                             &deviceInfoData);
      if ((nStatus != TRUE) || (index > 48))  // limit of 50
         break;
   }

   // check if anything to search for
   if (index == 0)
   {
      SetupDiDestroyDeviceInfoList(hdevClassInfo);
      //ERROR 
      return (SMALLINT) 0;
   }

   // Enumerate through DS9490s found, this time retrieving their symbolic links 
   // otherwise known as device interface handles, and saving them off in an array 
   // of structs for use later on.  These can be used later on to retrieve a WinUSB 
   // handle.

   // loop through DS2490-based devices
   for (i = 0; i < index; i++) 
   {
      nStatus = SetupDiEnumDeviceInterfaces(hdevClassInfo,
                                            0,
                                            (LPGUID)&GUID_DEVINTERFACE_DS2490,
                                            i,
                                            &deviceInfoData);

      // Call SetupDiGetDeviceInterfaceDetail just to get requiredLength of struct
      SetupDiGetDeviceInterfaceDetail(hdevClassInfo,
                                      &deviceInfoData,
                                      NULL,
                                      0,
                                      &requiredLength,
                                      NULL);

      // Allocate memory for deviceDetailData.
      deviceList[i].deviceDetailData = (PSP_INTERFACE_DEVICE_DETAIL_DATA) malloc(requiredLength);
      if (deviceList[i].deviceDetailData == NULL)
      {
         // ERROR -- memory not allocated
         SetupDiDestroyDeviceInfoList(hdevClassInfo);
         return (SMALLINT) 0;
      }

      // The cbSize member of struct should be the size of the struct
      deviceList[i].deviceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

	   // This second call to SetupDiGetDeviceInterfaceDetail gets 
	   // deviceList[i] populated with the all important 
	   // "device interface path", which is used to eventually get 
	   // a handle to WinUSB.
      SetupDiGetDeviceInterfaceDetail(hdevClassInfo,
                                      &deviceInfoData,
                                      deviceList[i].deviceDetailData,
                                      requiredLength,
                                      &requiredLength,
                                      NULL);
   }
   
   SetupDiDestroyDeviceInfoList(hdevClassInfo);
   return (SMALLINT) index;
}
