/************************************************************************************//**
* \file         pcanusb.c
* \brief        Peak PCAN-USB interface source file.
* \ingroup      PCanUsb
* \internal
*----------------------------------------------------------------------------------------
*                          C O P Y R I G H T
*----------------------------------------------------------------------------------------
*   Copyright (c) 2017  by Feaser    http://www.feaser.com    All rights reserved
*
*----------------------------------------------------------------------------------------
*                            L I C E N S E
*----------------------------------------------------------------------------------------
* This file is part of OpenBLT. OpenBLT is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* OpenBLT 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 have received a copy of the GNU General Public License along with OpenBLT. It
* should be located in ".\Doc\license.html". If not, contact Feaser to obtain a copy.
*
* \endinternal
****************************************************************************************/

/****************************************************************************************
* Include files
****************************************************************************************/
#include <assert.h>                         /* for assertions                          */
#include <stdint.h>                         /* for standard integer types              */
#include <stddef.h>                         /* for NULL declaration                    */
#include <stdbool.h>                        /* for boolean type                        */
#include <stdlib.h>                         /* for standard library                    */
#include <string.h>                         /* for string library                      */
#include "candriver.h"                      /* Generic CAN driver module               */
#include "pcanusb.h"                        /* Peak PCAN-USB interface                 */
#include <windows.h>                        /* for Windows API                         */
#include "PCANBasic.h"                      /* for PCAN-Basic API                      */


/***************************************************************************************
* Type definitions
****************************************************************************************/
/* Type definitions of the function in the PCAN-Basic API that this CAN interface uses.*/
typedef TPCANStatus (__stdcall * tPCanUsbLibFuncInitialize)(TPCANHandle, TPCANBaudrate, TPCANType, DWORD, WORD);
typedef TPCANStatus (__stdcall * tPCanUsbLibFuncpUninitialize)(TPCANHandle); 
typedef TPCANStatus (__stdcall * tPCanUsbLibFuncGetStatus)(TPCANHandle); 
typedef TPCANStatus (__stdcall * tPCanUsbLibFuncSetValue)(TPCANHandle, TPCANParameter, void*, DWORD); 
typedef TPCANStatus (__stdcall * tPCanUsbLibFuncRead)(TPCANHandle, TPCANMsg*, TPCANTimestamp*); 
typedef TPCANStatus (__stdcall * tPCanUsbLibFuncWrite)(TPCANHandle, TPCANMsg*); 
typedef TPCANStatus (__stdcall * tPCanUsbLibFuncFilterMessages)(TPCANHandle, DWORD, DWORD, TPCANMode);


/***************************************************************************************
* Function prototypes
****************************************************************************************/
/* CAN interface functions. */
static void PCanUsbInit(tCanSettings const * settings);
static void PCanUsbTerminate(void);
static bool PCanUsbConnect(void);
static void PCanUsbDisconnect(void);
static bool PCanUsbTransmit(tCanMsg const * msg);
static bool PCanUsbIsBusError(void);
static void PCanUsbRegisterEvents(tCanEvents const * events);
/* CAN message reception thread. */
static DWORD WINAPI PCanUsbReceptionThread(LPVOID pv);
/* PCAN-Basic library handling. */
static void PCanUsbLibLoadDll(void);
static void PCanUsbLibUnloadDll(void);
static TPCANStatus PCanUsbLibFuncInitialize(TPCANHandle Channel, TPCANBaudrate Btr0Btr1, 
                                            TPCANType HwType, DWORD IOPort, 
                                            WORD Interrupt);
static TPCANStatus PCanUsbLibFuncpUninitialize(TPCANHandle Channel);
static TPCANStatus PCanUsbLibFuncGetStatus(TPCANHandle Channel);
static TPCANStatus PCanUsbLibFuncSetValue(TPCANHandle Channel, TPCANParameter Parameter,
                                          void * Buffer, DWORD BufferLength);
static TPCANStatus PCanUsbLibFuncRead(TPCANHandle Channel, TPCANMsg * MessageBuffer, 
                                      TPCANTimestamp * TimestampBuffer);
static TPCANStatus PCanUsbLibFuncWrite(TPCANHandle Channel, TPCANMsg * MessageBuffer);
static TPCANStatus PCanUsbLibFuncFilterMessages(TPCANHandle Channel, DWORD FromID, 
                                                DWORD ToID, TPCANMode Mode);


/****************************************************************************************
* Local constant declarations
****************************************************************************************/
/** \brief CAN interface structure filled with Peak PCAN-USB specifics. */
static const tCanInterface pCanUsbInterface =
{
  PCanUsbInit,
  PCanUsbTerminate,
  PCanUsbConnect,
  PCanUsbDisconnect,
  PCanUsbTransmit,
  PCanUsbIsBusError,
  PCanUsbRegisterEvents
};

/** \brief PCAN-USB channel handle lookup table. The pCanUsbSettings.channel value can
 *         be used as the index.
 */
static const TPCANHandle pCanUsbChannelLookup[] =
{
  PCAN_USBBUS1,     /* PCAN-USB interface, channel 1.  */
  PCAN_USBBUS2,     /* PCAN-USB interface, channel 2.  */
  PCAN_USBBUS3,     /* PCAN-USB interface, channel 3.  */
  PCAN_USBBUS4,     /* PCAN-USB interface, channel 4.  */
  PCAN_USBBUS5,     /* PCAN-USB interface, channel 5.  */
  PCAN_USBBUS6,     /* PCAN-USB interface, channel 6.  */
  PCAN_USBBUS7,     /* PCAN-USB interface, channel 7.  */
  PCAN_USBBUS8,     /* PCAN-USB interface, channel 8.  */
  PCAN_USBBUS9,     /* PCAN-USB interface, channel 9.  */
  PCAN_USBBUS10,    /* PCAN-USB interface, channel 10. */ 
  PCAN_USBBUS11,    /* PCAN-USB interface, channel 11. */
  PCAN_USBBUS12,    /* PCAN-USB interface, channel 12. */
  PCAN_USBBUS13,    /* PCAN-USB interface, channel 13. */
  PCAN_USBBUS14,    /* PCAN-USB interface, channel 14. */
  PCAN_USBBUS15,    /* PCAN-USB interface, channel 15. */
  PCAN_USBBUS16     /* PCAN-USB interface, channel 16. */
};


/****************************************************************************************
* Local data declarations
****************************************************************************************/
/** \brief The settings to use in this CAN interface. */
static tCanSettings pCanUsbSettings;

/** \brief List with callback functions that this driver should use. */
static tCanEvents * pCanUsbEventsList;

/** \brief Total number of event entries into the \ref pCanUsbEventsList list. */
static uint32_t pCanUsbEventsEntries;

/** \brief Handle to the PCAN-Basic dynamic link library. */
static HINSTANCE pCanUsbDllHandle;

/** \brief Function pointer to the PCAN-Basic Initialize function. */
static tPCanUsbLibFuncInitialize pCanUsbLibFuncInitializePtr;

/** \brief Function pointer to the PCAN-Basic Uninitialize function. */
static tPCanUsbLibFuncpUninitialize pCanUsbLibFuncpUninitializePtr;

/** \brief Function pointer to the PCAN-Basic GetStatus function. */
static tPCanUsbLibFuncGetStatus pCanUsbLibFuncGetStatusPtr;

/** \brief Function pointer to the PCAN-Basic SetValue function. */
static tPCanUsbLibFuncSetValue pCanUsbLibFuncSetValuePtr;

/** \brief Function pointer to the PCAN-Basic Read function. */
static tPCanUsbLibFuncRead pCanUsbLibFuncReadPtr;

/** \brief Function pointer to the PCAN-Basic Write function. */
static tPCanUsbLibFuncWrite pCanUsbLibFuncWritePtr;

/** \brief Function pointer to the PCAN-Basic FilterMessages function. */
static tPCanUsbLibFuncFilterMessages pCanUsbLibFuncFilterMessagesPtr;

/** \brief Handle for the event to terminate the reception thread. */
static HANDLE pCanUsbTerminateEvent;

/** \brief Handle for a CAN related event. */
static HANDLE pCanUsbCanEvent;

/** \brief Handle for the CAN reception thread. */
static HANDLE pCanUsbRxThreadHandle;


/***********************************************************************************//**
** \brief     Obtains a pointer to the CAN interface structure, so that it can be linked
**            to the generic CAN driver module.
** \return    Pointer to CAN interface structure.
**
****************************************************************************************/
tCanInterface const * PCanUsbGetInterface(void)
{
  return &pCanUsbInterface;
} /*** end of PCanUsbGetInterface ***/


/************************************************************************************//**
** \brief     Initializes the CAN interface.
** \param     settings Pointer to the CAN interface settings.
**
****************************************************************************************/
static void PCanUsbInit(tCanSettings const * settings)
{
  char * canDeviceName;

  /* Initialize locals. */
  pCanUsbEventsList = NULL;
  pCanUsbEventsEntries = 0;
  pCanUsbTerminateEvent = NULL;
  pCanUsbCanEvent = NULL;
  pCanUsbRxThreadHandle = NULL;
  pCanUsbDllHandle = NULL;
  /* Reset library function pointers. */
  pCanUsbLibFuncInitializePtr = NULL;
  pCanUsbLibFuncpUninitializePtr = NULL;
  pCanUsbLibFuncGetStatusPtr = NULL;
  pCanUsbLibFuncSetValuePtr = NULL;
  pCanUsbLibFuncReadPtr = NULL;
  pCanUsbLibFuncWritePtr = NULL;
  pCanUsbLibFuncFilterMessagesPtr = NULL;
  /* Reset CAN interface settings. */
  pCanUsbSettings.devicename = "";
  pCanUsbSettings.channel = 0;
  pCanUsbSettings.baudrate = CAN_BR500K;
  pCanUsbSettings.code = 0x00000000u;
  pCanUsbSettings.mask = 0x00000000u;

  /* Check parameters. */
  assert(settings != NULL);

  /* Only continue with valid parameters. */
  if (settings != NULL) /*lint !e774 */
  {
    /* Shallow copy the CAN interface settings for later usage. */
    pCanUsbSettings = *settings;
    /* The devicename is a pointer and it is not gauranteed that it stays valid so we need
     * to deep copy this one. note the +1 for '\0' in malloc.
     */
    assert(settings->devicename != NULL);
    if (settings->devicename != NULL) /*lint !e774 */
    {
      canDeviceName = malloc(strlen(settings->devicename) + 1);
      assert(canDeviceName != NULL);
      if (canDeviceName != NULL) /*lint !e774 */
      {
        strcpy(canDeviceName, settings->devicename);
        pCanUsbSettings.devicename = canDeviceName;
      }
    }
    /* Perform initialization of PCAN-Basic API. */
    PCanUsbLibLoadDll();
  }
} /*** end of PCanUsbInit ***/


/************************************************************************************//**
** \brief     Terminates the CAN interface.
**
****************************************************************************************/
static void PCanUsbTerminate(void)
{
  /* Perform termination of PCAN-Basic API. */
  PCanUsbLibUnloadDll();
  /* Release memory that was allocated for storing the device name. */
  if (pCanUsbSettings.devicename != NULL)
  {
    free((char *)pCanUsbSettings.devicename);
  }
  /* Reset CAN interface settings. */
  pCanUsbSettings.devicename = "";
  pCanUsbSettings.channel = 0;
  pCanUsbSettings.baudrate = CAN_BR500K;
  pCanUsbSettings.code = 0x00000000u;
  pCanUsbSettings.mask = 0x00000000u;
  /* Release memory that was allocated for CAN events and reset the entry count. */
  if ( (pCanUsbEventsList != NULL) && (pCanUsbEventsEntries != 0) )
  {
    free(pCanUsbEventsList);
    pCanUsbEventsEntries = 0;
  }
} /*** end of PCanUsbTerminate ***/


/************************************************************************************//**
** \brief     Connects the CAN interface.
** \return    True if connected, false otherwise.
**
****************************************************************************************/
static bool PCanUsbConnect(void)
{
  bool result = false;
  bool baudrateSupported = true;
  bool channelSupported;
  bool libInitialized = false;
  TPCANBaudrate baudrate = PCAN_BAUD_500K;
  uint32_t iBuffer;

  /* Convert the baudrate to a value supported by the PCAN-Basic API. */
  switch (pCanUsbSettings.baudrate)
  {
    case CAN_BR10K:
      baudrate = PCAN_BAUD_10K;
      break;
    case CAN_BR20K:
      baudrate = PCAN_BAUD_20K;
      break;
    case CAN_BR50K:
      baudrate = PCAN_BAUD_50K;
      break;
    case CAN_BR100K:
      baudrate = PCAN_BAUD_100K;
      break;
    case CAN_BR125K:
      baudrate = PCAN_BAUD_125K;
      break;
    case CAN_BR250K:
      baudrate = PCAN_BAUD_250K;
      break;
    case CAN_BR500K:
      baudrate = PCAN_BAUD_500K;
      break;
    case CAN_BR800K:
      baudrate = PCAN_BAUD_800K;
      break;
    case CAN_BR1M:
      baudrate = PCAN_BAUD_1M;
      break;
    default:
      baudrateSupported = false;
      break;
  }

  /* Validate channel index. For this CAN interface, the channel index specifies the
   * PCAN-USB device, in case multiple are connected.
   */
  channelSupported = (pCanUsbSettings.channel < (sizeof(pCanUsbChannelLookup) / 
                                                 sizeof(pCanUsbChannelLookup[0])));

  /* Note that the device name itself is not needed anymore at this point, it was only
   * needed by the CAN driver to link the correct interface (this one). Check settings.
   */
  assert(baudrateSupported);
  assert(channelSupported);

  /* Only continue with valid settings. */
  if ( (baudrateSupported) && (channelSupported) )
  {
    /* Init result code to success and only negate it on detection of error. */
    result = true;
    /* Connect to the CAN hardware interface. */
    if (PCanUsbLibFuncInitialize(pCanUsbChannelLookup[pCanUsbSettings.channel],
                                 baudrate, 0, 0, 0) != PCAN_ERROR_OK)
    { 
      result = false;
    }
    else
    {
      libInitialized = true;
    }

    /* Create the terminate event handle used in the reception thread. */
    if (result)
    {
      pCanUsbTerminateEvent = CreateEvent(NULL, TRUE, FALSE, "");
      if (pCanUsbTerminateEvent == NULL)
      {
        result = false;
      }
    }
    
    /* Create the CAN event handle used in the reception thread. */
    if (result)
    {
      pCanUsbCanEvent = CreateEvent(NULL, FALSE, FALSE, "");
      if (pCanUsbCanEvent == NULL)
      {
        result = false;
      }
    }

    /* Enable the bus off auto reset. */
    if (result)
    {
      iBuffer = PCAN_PARAMETER_ON;
      if (PCanUsbLibFuncSetValue(pCanUsbChannelLookup[pCanUsbSettings.channel],
                                 PCAN_BUSOFF_AUTORESET, &iBuffer,
                                 sizeof(iBuffer)) != PCAN_ERROR_OK)
      {
        result = false;
      }
    }

    /* Configure reception acceptance filter, if it is not supposed to be fully open. */
    if ( (result) && (pCanUsbSettings.mask != 0x00000000u) )
    {
      /* Configuration of the reception acceptance filter in the PCAN-Basic library does
       * not use a mask and code approach. Instead it has an API function for setting 
       * the filter by specifing an identifier range. This means the mask and code pair
       * should be used to calculate the lowest and highest identifier that should pass
       * the filter. It is not 100% accurate. In some cases more identifiers can pass
       * the filter than desired, but there is no other option:
       *   Lowest CAN ID  = (code &  mask) & 0x1fffffff
       *   Highest CAN ID = (code | ~mask) & 0x1fffffff
       */
      uint32_t lowestCanID  = (pCanUsbSettings.code &  pCanUsbSettings.mask)  & 0x1fffffffu;
      uint32_t highestCanID = (pCanUsbSettings.code | ~pCanUsbSettings.mask)  & 0x1fffffffu;
      /* Use bit logic to determine if the filter should accept standard 11-bit and/or
       * extended 29-bit identifiers:
       *   acceptStdId = ((mask & code & CAN_MSG_EXT_ID_MASK) == 0)
       *   acceptExtId = ((mask & code & CAN_MSG_EXT_ID_MASK) != 0) || 
       *                 ((mask & CAN_MSG_EXT_ID_MASK) == 0)
       */
      bool acceptStdID = ((pCanUsbSettings.mask & pCanUsbSettings.code & CAN_MSG_EXT_ID_MASK) == 0);
      bool acceptExtID = ((pCanUsbSettings.mask & pCanUsbSettings.code & CAN_MSG_EXT_ID_MASK) != 0) ||
                         ((pCanUsbSettings.mask & CAN_MSG_EXT_ID_MASK) == 0);
      /* Configure acceptance filter for standard 11-bit identifiers. */
      if (acceptStdID)
      {
        if (PCanUsbLibFuncFilterMessages(pCanUsbChannelLookup[pCanUsbSettings.channel],
                                         lowestCanID, highestCanID,
                                         PCAN_MODE_STANDARD) != PCAN_ERROR_OK)
        {
          result = false;
        }
      }
      /* Configure acceptance filter for extended 29-bit identifiers. */
      if ( (acceptExtID) && (result) )
      {
        if (PCanUsbLibFuncFilterMessages(pCanUsbChannelLookup[pCanUsbSettings.channel],
                                         lowestCanID, highestCanID,
                                         PCAN_MODE_EXTENDED) != PCAN_ERROR_OK)
        {
          result = false;
        }
      }
    }

    /* Register the event reception handle. */
    if (result)
    {
      if (PCanUsbLibFuncSetValue(pCanUsbChannelLookup[pCanUsbSettings.channel],
                                 PCAN_RECEIVE_EVENT, &pCanUsbCanEvent,
                                 sizeof(pCanUsbCanEvent)) != PCAN_ERROR_OK)
      {
        result = false;
      }
    }

    /* Start the reception thread as the last step. */
    if (result)
    {
      pCanUsbRxThreadHandle = CreateThread(NULL, 0, PCanUsbReceptionThread,
                                           NULL, 0, NULL);
      if (pCanUsbRxThreadHandle == NULL)
      {
        result = false;
      }
    }
  }

  /* Clean-up in case an error occurred. */
  if (!result)
  {
    if (libInitialized)
    { 
      /* Uninitialize the library. */
      (void)PCanUsbLibFuncpUninitialize(pCanUsbChannelLookup[pCanUsbSettings.channel]);
    }
    if (pCanUsbTerminateEvent != NULL)
    {
      /* Close the event handle. */
      (void)CloseHandle(pCanUsbTerminateEvent);
      pCanUsbTerminateEvent = NULL;
    }
    if (pCanUsbCanEvent != NULL)
    {
      /* Close the event handle. */
      (void)CloseHandle(pCanUsbCanEvent);
      pCanUsbCanEvent = NULL;
    }
  }

  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbConnect ***/


/************************************************************************************//**
** \brief     Disconnects the CAN interface.
**
****************************************************************************************/
static void PCanUsbDisconnect(void)
{
   /* Stop the reception thread. */
  if (pCanUsbRxThreadHandle != NULL)
  {
    /* Trigger event to request the reception thread to stop. */
    (void)SetEvent(pCanUsbTerminateEvent);
    /* Wait for the thread to signal termination. */
    (void)WaitForSingleObject(pCanUsbRxThreadHandle, INFINITE);
    /* Close the thread handle. */
    (void)CloseHandle(pCanUsbRxThreadHandle);
  }

  /* Close the terminate event handle. */
  if (pCanUsbTerminateEvent != NULL)
  {
    (void)CloseHandle(pCanUsbTerminateEvent);
    pCanUsbTerminateEvent = NULL;
  }
  /* Close the CAN event handle. */
  if (pCanUsbCanEvent != NULL)
  {
    (void)CloseHandle(pCanUsbCanEvent);
    pCanUsbCanEvent = NULL;
  }
  /* Disconnect from the CAN interface. */
  (void)PCanUsbLibFuncpUninitialize(pCanUsbChannelLookup[pCanUsbSettings.channel]);
} /*** end of PCanUsbDisconnect ***/


/************************************************************************************//**
** \brief     Submits a message for transmission on the CAN bus.
** \param     msg Pointer to CAN message structure.
** \return    True if successful, false otherwise.
**
****************************************************************************************/
static bool PCanUsbTransmit(tCanMsg const * msg)
{
  bool result = false;
  TPCANMsg msgBuf;
  tCanEvents const * pEvents;
 
  /* Check parameters. */
  assert(msg != NULL);

  /* Only continue with valid parameters. */
  if (msg != NULL) /*lint !e774 */
  {
    /* Convert the message to a type supported by the PCAN-Basic API. */
    if ((msg->id & CAN_MSG_EXT_ID_MASK) == 0)
    {
      msgBuf.ID = msg->id & 0x7ffu;
      msgBuf.MSGTYPE = PCAN_MESSAGE_STANDARD;
    }
    else
    {
      msgBuf.ID = msg->id & 0x1fffffffu;
      msgBuf.MSGTYPE = PCAN_MESSAGE_EXTENDED;
    }
    msgBuf.LEN = ((msg->dlc <= CAN_MSG_MAX_LEN) ? msg->dlc : CAN_MSG_MAX_LEN);
    for (uint8_t idx = 0; idx < msgBuf.LEN; idx++)
    {
      msgBuf.DATA[idx] = msg->data[idx];
    }
    
    /* Submit CAN message for transmission. */
    if (PCanUsbLibFuncWrite(pCanUsbChannelLookup[pCanUsbSettings.channel],
                            &msgBuf) == PCAN_ERROR_OK)
    {
      /* Update result value to success. */
      result = true;
      /* Trigger transmit complete event(s). */
      pEvents = pCanUsbEventsList;
      for (uint32_t idx = 0; idx < pCanUsbEventsEntries; idx++)
      {
        if (pEvents != NULL)
        {
          if (pEvents->MsgTxed != NULL)
          {
            pEvents->MsgTxed(msg);
          }
          /* Move on to the next entry in the list. */
          pEvents++;
        }
      }
    }
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbTransmit ***/


/************************************************************************************//**
** \brief     Checks if a bus off or bus heavy situation occurred.
** \return    True if a bus error situation was detected, false otherwise.
**
****************************************************************************************/
static bool PCanUsbIsBusError(void)
{
  bool result = false;
  TPCANStatus status;

  /* Obtain the status information. */
  status = PCanUsbLibFuncGetStatus(pCanUsbChannelLookup[pCanUsbSettings.channel]);

  /* Process the status to detect bus off or bus heavy. */
  if ((status == PCAN_ERROR_BUSOFF) || (status == PCAN_ERROR_BUSHEAVY))
  {
    result = true;
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbIsBusError ***/


/************************************************************************************//**
** \brief     Registers the event callback functions that should be called by the CAN
**            interface.
** \param     events Pointer to structure with event callback function pointers.
**
****************************************************************************************/
static void PCanUsbRegisterEvents(tCanEvents const * events)
{
  /* Check parameters. */
  assert(events != NULL);

  /* Only continue with valid parameters. */
  if (events != NULL) /*lint !e774 */
  {
    /* Increase length of the list to make space for one more event entry. Note that
     * it is okay to call realloc with a NULL pointer. In this case it simply behaves
     * as malloc. 
     */
    pCanUsbEventsList = realloc(pCanUsbEventsList,
                                (sizeof(tCanEvents) * (pCanUsbEventsEntries + 1)));
    /* Assert reallocation. */
    assert(pCanUsbEventsList != NULL);
    /* Only continue if reallocation was successful. */
    if (pCanUsbEventsList != NULL)
    {
      /* Increment events entry count. */
      pCanUsbEventsEntries++;
      /* Store the events in the new entry. */
      pCanUsbEventsList[pCanUsbEventsEntries - 1] = *events;
    }
    /* Reallocation failed. */
    else
    {
      /* Reset events entry count. */
      pCanUsbEventsEntries = 0;
    }
  }
} /*** end of PCanUsbRegisterEvents ***/


/************************************************************************************//**
** \brief     CAN message reception thread.
** \param     pv Pointer to thread parameters.
** \return    Thread exit code.
**
****************************************************************************************/
static DWORD WINAPI PCanUsbReceptionThread(LPVOID pv)
{
  DWORD waitResult;
  HANDLE handles[] = 
  { 
    pCanUsbCanEvent, 
    pCanUsbTerminateEvent 
  };
  bool running = true;
  TPCANMsg rxLibMsg;
  tCanMsg rxMsg;
  tCanEvents const * pEvents;

  /* Parameter not used. */
  (void)pv;

  /* Enter thread's infinite loop. */
  while (running)
  {
    waitResult = WaitForMultipleObjects(sizeof(handles)/sizeof(handles[0]), handles,
                                        FALSE, INFINITE);
    switch (waitResult) 
    {
      /* CAN reception event. */
      case WAIT_OBJECT_0 + 0: /*lint !e835 */
        /* Empty out the queue with received events. */
        while (PCanUsbLibFuncRead(pCanUsbChannelLookup[pCanUsbSettings.channel], 
               &rxLibMsg, NULL) == PCAN_ERROR_OK)
        {
          /* Only process events that contain a CAN message with a 11-bit or 29-bit
           * identifier. 
           */
          if ((rxLibMsg.MSGTYPE == PCAN_MESSAGE_STANDARD) || 
              (rxLibMsg.MSGTYPE == PCAN_MESSAGE_EXTENDED))
          {
            /* Convert CAN mesage from PCAN-Basic format to the one of the CAN driver
             * module.
             */
            rxMsg.id = rxLibMsg.ID;
            if (rxLibMsg.MSGTYPE == PCAN_MESSAGE_EXTENDED)
            {
              rxMsg.id |= CAN_MSG_EXT_ID_MASK;
            }
            rxMsg.dlc = rxLibMsg.LEN;
            for (uint8_t idx = 0; idx < rxMsg.dlc; idx++)
            {
              rxMsg.data[idx] = rxLibMsg.DATA[idx];
            }

            /* Trigger message reception event(s). */
            pEvents = pCanUsbEventsList;
            for (uint32_t idx = 0; idx < pCanUsbEventsEntries; idx++)
            {
              if (pEvents != NULL)
              {
                if (pEvents->MsgRxed != NULL)
                {
                  pEvents->MsgRxed(&rxMsg);
                }
                /* Move on to the next entry in the list. */
                pEvents++;
              }
            }
          }
        }
        break;

      /* Termination event. */
      default:
      case WAIT_OBJECT_0 + 1: /*lint !e835 */
        /* Stop thread. */
        running = false;
        break;
    }
  }
  /* Exit thread. */
  return 0;
} /*** end of PCanUsbReceptionThread ***/


/************************************************************************************//**
** \brief     Loads the PCAN-Basic DLL and initializes the API function pointers.
**
****************************************************************************************/
static void PCanUsbLibLoadDll(void)
{
  /* Start out by resetting the API function pointers. */
  pCanUsbLibFuncInitializePtr = NULL;
  pCanUsbLibFuncpUninitializePtr = NULL;
  pCanUsbLibFuncGetStatusPtr = NULL;
  pCanUsbLibFuncSetValuePtr = NULL;
  pCanUsbLibFuncReadPtr = NULL;
  pCanUsbLibFuncWritePtr = NULL;
  pCanUsbLibFuncFilterMessagesPtr = NULL;

  /* Attempt to load the library and obtain a handle to it. */
  pCanUsbDllHandle = LoadLibrary("PCANBasic");

  /* Assert libary handle. */
  assert(pCanUsbDllHandle != NULL);

  /* Only continue if the library was successfully loaded */
  if (pCanUsbDllHandle != NULL) /*lint !e774 */
  {
    /* Set CAN_Initialize function pointer. */
    pCanUsbLibFuncInitializePtr = (tPCanUsbLibFuncInitialize)GetProcAddress(pCanUsbDllHandle, "CAN_Initialize");
    /* Set CAN_Uninitialize function pointer. */
    pCanUsbLibFuncpUninitializePtr = (tPCanUsbLibFuncpUninitialize)GetProcAddress(pCanUsbDllHandle, "CAN_Uninitialize");
    /* Set CAN_GetStatus function pointer. */
    pCanUsbLibFuncGetStatusPtr = (tPCanUsbLibFuncGetStatus)GetProcAddress(pCanUsbDllHandle, "CAN_GetStatus");
    /* Set CAN_SetValue function pointer. */
    pCanUsbLibFuncSetValuePtr = (tPCanUsbLibFuncSetValue)GetProcAddress(pCanUsbDllHandle, "CAN_SetValue");
    /* Set CAN_Read function pointer. */
    pCanUsbLibFuncReadPtr = (tPCanUsbLibFuncRead)GetProcAddress(pCanUsbDllHandle, "CAN_Read");
    /* Set CAN_Write function pointer. */
    pCanUsbLibFuncWritePtr = (tPCanUsbLibFuncWrite)GetProcAddress(pCanUsbDllHandle, "CAN_Write");
    /* Set CAN_FilterMessages function pointer. */
    pCanUsbLibFuncFilterMessagesPtr = (tPCanUsbLibFuncFilterMessages)GetProcAddress(pCanUsbDllHandle, "CAN_FilterMessages");
  }
} /*** end of PCanUsbLibLoadDll ***/


/************************************************************************************//**
** \brief     Unloads the PCAN-Basic DLL and resets the API function pointers.
**
****************************************************************************************/
static void PCanUsbLibUnloadDll(void)
{
  /* Reset the API function pointers. */
  pCanUsbLibFuncInitializePtr = NULL;
  pCanUsbLibFuncpUninitializePtr = NULL;
  pCanUsbLibFuncGetStatusPtr = NULL;
  pCanUsbLibFuncSetValuePtr = NULL;
  pCanUsbLibFuncReadPtr = NULL;
  pCanUsbLibFuncWritePtr = NULL;
  pCanUsbLibFuncFilterMessagesPtr = NULL;

  /* Unload the library and invalidate its handle. */
  if (pCanUsbDllHandle != NULL) 
  {
    (void)FreeLibrary(pCanUsbDllHandle);
    pCanUsbDllHandle = NULL;
  }
} /*** end of PCanUsbLibUnloadDll ***/


/************************************************************************************//**
** \brief     Initializes a PCAN Channel.
** \param     Channel The handle of a PCAN Channel.
** \param     Btr0Btr1 The speed for the communication (BTR0BTR1 code).
** \param     HwType The type of the Non-Plug-and-Play hardware and its operation mode.
** \param     IOPort The I/O address for the parallel port of the Non-Plug-and-Play 
**            hardware.
** \param     Interrupt The Interrupt number of the parallel port of the Non-Plug-
**            and-Play hardware. 
** \return    The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on 
**            success.
**
****************************************************************************************/
static TPCANStatus PCanUsbLibFuncInitialize(TPCANHandle Channel, TPCANBaudrate Btr0Btr1, 
                                            TPCANType HwType, DWORD IOPort, 
                                            WORD Interrupt)
{
  /* set result to error. */
  TPCANStatus result = PCAN_ERROR_INITIALIZE;

  /* Check function pointer and library handle. */
  assert(pCanUsbLibFuncInitializePtr != NULL);
  assert(pCanUsbDllHandle != NULL);

  /* Only continue with valid function pointer and library handle. */
  if ((pCanUsbLibFuncInitializePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */
  {
    /* Call library function. */
    result = pCanUsbLibFuncInitializePtr(Channel, Btr0Btr1, HwType, IOPort, Interrupt);
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbLibFuncInitialize ***/


/************************************************************************************//**
** \brief     Uninitializes a PCAN Channel.
** \param     Channel The handle of a PCAN Channel.
** \return    The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on 
**            success.
**
****************************************************************************************/
static TPCANStatus PCanUsbLibFuncpUninitialize(TPCANHandle Channel)
{
  /* set result to error. */
  TPCANStatus result = PCAN_ERROR_INITIALIZE;

  /* Check function pointer and library handle. */
  assert(pCanUsbLibFuncpUninitializePtr != NULL);
  assert(pCanUsbDllHandle != NULL);

  /* Only continue with valid function pointer and library handle. */
  if ((pCanUsbLibFuncpUninitializePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */
  {
    /* Call library function. */
    result = pCanUsbLibFuncpUninitializePtr(Channel);
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbLibFuncpUninitialize ***/


/************************************************************************************//**
** \brief     Gets the current BUS status of a PCAN Channel.
** \param     Channel The handle of a PCAN Channel.
** \return    The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on 
**            success.
**
****************************************************************************************/
static TPCANStatus PCanUsbLibFuncGetStatus(TPCANHandle Channel)
{
  /* set result to error. */
  TPCANStatus result = PCAN_ERROR_INITIALIZE;

  /* Check function pointer and library handle. */
  assert(pCanUsbLibFuncGetStatusPtr != NULL);
  assert(pCanUsbDllHandle != NULL);

  /* Only continue with valid function pointer and library handle. */
  if ((pCanUsbLibFuncGetStatusPtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */
  {
    /* Call library function. */
    result = pCanUsbLibFuncGetStatusPtr(Channel);
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbLibFuncGetStatus ***/


/************************************************************************************//**
** \brief     Sets a configuration or information value within a PCAN Channel.
** \param     Channel The handle of a PCAN Channel.
** \param     Parameter The code of the value to be set .
** \param     Buffer The buffer containing the value to be set.
** \param     BufferLength The length in bytes of the given buffer.
** \return    The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on 
**            success.
**
****************************************************************************************/
static TPCANStatus PCanUsbLibFuncSetValue(TPCANHandle Channel, TPCANParameter Parameter,
                                          void * Buffer, DWORD BufferLength)
{
  /* set result to error. */
  TPCANStatus result = PCAN_ERROR_INITIALIZE;

  /* Check function pointer and library handle. */
  assert(pCanUsbLibFuncSetValuePtr != NULL);
  assert(pCanUsbDllHandle != NULL);

  /* Only continue with valid function pointer and library handle. */
  if ((pCanUsbLibFuncSetValuePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */
  {
    /* Call library function. */
    result = pCanUsbLibFuncSetValuePtr(Channel, Parameter, Buffer, BufferLength);
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbLibFuncSetValue ***/


/************************************************************************************//**
** \brief     Reads a CAN message from the receive queue of a PCAN Channel.
** \param     Channel The handle of a PCAN Channel.
** \param     MessageBuffer A TPCANMsg buffer to store the CAN message. 
** \param     TimestampBuffer A TPCANTimestamp buffer to get the reception time of the 
**            message. 
** \return    The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on 
**            success.
**
****************************************************************************************/
static TPCANStatus PCanUsbLibFuncRead(TPCANHandle Channel, TPCANMsg * MessageBuffer, 
                                      TPCANTimestamp * TimestampBuffer)
{
  /* set result to error. */
  TPCANStatus result = PCAN_ERROR_INITIALIZE;

  /* Check function pointer and library handle. */
  assert(pCanUsbLibFuncReadPtr != NULL);
  assert(pCanUsbDllHandle != NULL);

  /* Only continue with valid function pointer and library handle. */
  if ((pCanUsbLibFuncReadPtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */
  {
    /* Call library function. */
    result = pCanUsbLibFuncReadPtr(Channel, MessageBuffer, TimestampBuffer);
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbLibFuncRead ***/


/************************************************************************************//**
** \brief     Transmits a CAN message.
** \param     Channel The handle of a PCAN Channel.
** \param     MessageBuffer A TPCANMsg buffer containing the CAN message to be sent. 
** \return    The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on 
**            success.
**
****************************************************************************************/
static TPCANStatus PCanUsbLibFuncWrite(TPCANHandle Channel, TPCANMsg * MessageBuffer)
{
  /* set result to error. */
  TPCANStatus result = PCAN_ERROR_INITIALIZE;

  /* Check function pointer and library handle. */
  assert(pCanUsbLibFuncWritePtr != NULL);
  assert(pCanUsbDllHandle != NULL);

  /* Only continue with valid function pointer and library handle. */
  if ((pCanUsbLibFuncWritePtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */
  {
    /* Call library function. */
    result = pCanUsbLibFuncWritePtr(Channel, MessageBuffer);
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbLibFuncWrite ***/


/************************************************************************************//**
** \brief     Configures the reception filter.
** \param     Channel The handle of a PCAN Channel.
** \param     FromID The lowest CAN ID wanted to be received.
** \param     ToID The highest CAN ID wanted to be received. 
** \param     Mode The type of the filter being set.
** \return    The return value is a TPCANStatus code. PCAN_ERROR_OK is returned on 
**            success.
**
****************************************************************************************/
static TPCANStatus PCanUsbLibFuncFilterMessages(TPCANHandle Channel, DWORD FromID,
                                                DWORD ToID, TPCANMode Mode)
{
  /* set result to error. */
  TPCANStatus result = PCAN_ERROR_INITIALIZE;

  /* Check function pointer and library handle. */
  assert(pCanUsbLibFuncFilterMessagesPtr != NULL);
  assert(pCanUsbDllHandle != NULL);

  /* Only continue with valid function pointer and library handle. */
  if ((pCanUsbLibFuncFilterMessagesPtr != NULL) && (pCanUsbDllHandle != NULL)) /*lint !e774 */
  {
    /* Call library function. */
    result = pCanUsbLibFuncFilterMessagesPtr(Channel, FromID, ToID, Mode);
  }
  /* Give the result back to the caller. */
  return result;
} /*** end of PCanUsbLibFuncFilterMessages ***/


/*********************************** end of pcanusb.c **********************************/

